Effective STL 条款39

原创 2004年03月05日 13:47:00

条款39:用纯函数做判断式

我讨厌为你做这些,但我们必须从一个简短的词汇课开始:

  • 判断式(predicate)是返回bool(或者其他可以隐式转化为bool的东西)。判断式在STL中广泛使用。标准关联容器的比较函数是判断式,判断式函数常常作为参数传递给算法,比如find_if和多种排序算法。(排序算法的概览可以在条款31找到。)
  • 纯函数是返回值只依赖于参数的函数。如果f是一个纯函数,x和y是对象,f(x, y)的返回值仅当x或y的值改变的时候才会改变。
    在C++中,由纯函数引用的所有数据不是作为参数传进的就是在函数生存期内是常量。(一般,这样的常量应该声明为const。)如果一个纯函数引用的数据在不同次调用中可能改变,在不同的时候用同样的参数调用这个函数可能导致不同的结果,那就与纯函数的定义相反。

现在已经很清楚用纯函数作判断式是什么意思了。我要做的所有事情就是使你相信我的建议是有根据的。要帮我完成这件事,我希望你能原谅我再增加一个术语所给你带来的负担。

  • 一个判断式类是一个仿函数类,它的operator()函数是一个判断式,也就是,它的operator()返回true或false。正如你可以预料到的,任何STL想要一个判断式的地方,它都会接受一个真的判断式或一个判断式类对象。

就这些了,我保证!现在我们已经准备好学习为什么这个条款提供了有遵循价值的指引。

条款38解释了函数对象是传值,所以你应该设计可以拷贝的函数对象。用于判断式的函数对象,有另一个理由设计当它们拷贝时行为良好。算法可能拷贝仿函数,在使用前暂时保存它们,而且有些算法实现利用了这个自由。这个论点的一个重要结果是判断式函数必须是纯函数

想知道这是为什么,先让我们假设你想要违反这个约束。考虑下面(坏的实现)的判断式类。不管传递的是什么参数,它严格地只返回一次true:第三次被调用的时候。其他时候它返回假。

class BadPredicate:    // 关于这个基类的更多信息
 public unary_function<Widget, bool> { // 请参见条款40
public:
 BadPredicate(): timesCalled(0) {}  // 把timesCalled初始化为0
 bool operator()(const Widget&)
 {
  return ++timesCalled == 3;
 }
private:
 size_t timesCalled;
};

假设我们用这个类来从一个vector<Widget>中除去第三个Widget:

vector<Widget> vw;    // 建立vector,然后
     // 放一些Widgets进去
vw.erase(remove_if(vw.begin(),  // 去掉第三个Widget;
   vw.end(),  // 关于erase和remove_if的关系
   BadPredicate()), // 请参见条款32
   vw.end());

这段代码看起来很合理,但对于很多STL实现,它不仅会从vw中除去第三个元素,它也会除去第六个!

要知道这是怎么发生的,就该看看remove_if一般是怎么实现的。记住remove_if不是一定要这么实现:

template <typename FwdIterator, typename Predicate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p)
{
 begin = find_if(begin, end, p);
 if (begin == end) return begin;
 else {
  FwdIterator next = begin;
  return remove_copy_if(++next, end. begin, p);
 }
}

这段代码的细节不重要,但注意判断式p先传给find_if,后传给remove_copy_if。当然,在两种情况中,p是传值——是拷贝——到那些算法中的。(技术上说,这不需要是真的,但实际上,是真的。详细资料请参考条款38。)

最初调用remove_if(用户代码中要从vw中除去第三个元素的那次调用)建立一个匿名BadPredicate对象,它把内部的timesCalled成员清零。这个对象(在remove_if内部叫做p)然后被拷贝到find_if,所以find_if也接收了一个timesCalled等于0的BadPredicate对象。find_if“调用”那个对象直到它返回true,所以调用了三次,find_if然后返回控制权到remove_if。remove_if继续运行后面的调用remove_copy_if,传p的另一个拷贝作为一个判断式。但p的timesCalled成员仍然是0!find_if没有调用p,它调用的只是p的拷贝。结果,第三次remove_copy_if调用它的判断式,它也将会返回true。这就是为什么remove_if最终会从vw中删除两个Widgets而不是一个。

最简单的使你自己不摔跟头而进入语言陷阱的方法是在判断式类中把你的operator()函数声明为const。如果你这么做了,你的编译器不会让你改变任何类数据成员。

class BadPredicate:
 public unary_function<Widget, bool> {
public:
 bool operator()(const Widget&) const
 {
  return ++timesCalled == 3;  // 错误!在const成员函数中
 }     // 不能改变局部数据
};

因为这是避免我们刚测试过的问题的一个直截了当的方法,我几乎可以把本条款的题目改为“在判断式类中使operator()成为const”。但那走得不够远。甚至const成员函数可以访问multable数据成员、非const局部静态对象、非const类静态对象、名字空间域的非const对象和非const全局对象。一个设计良好的判断式类也保证它的operator()函数独立于任何那类对象。在判断式类中把operator()声明为const对于正确的行为来说是必要的,但不够充分。一个行为良好的operator()当然是const,但不只如此。它也得是一个纯函数。

本条款的前面,我强调了任何STL想要一个判断式的地方,它都会接受一个真的判断式或一个判断式类对象。它在两个方向上都是对的。在STL任何可以接受一个判断式类对象的地方,一个判断式函数(可能由ptr_fun改变——参见条款41)也是受欢迎的。你现在明白判断式类中的operator()函数应该是纯函数,所以这个限制也扩展到判断式函数。作为一个判断式,这个函数和从BadPredicate类产生的对象一样糟:

bool anotherBadPredicate(const Widget&, const Widget&)
{
 static int timesCalled = 0;   // 不!不!不!不!不!不!不!
 return ++timesCalled == 3;   // 判断式应该是纯函数,
}      // 纯函数没有状态

不管你怎么写你的判断式,它们都应该是纯函数。

《Effective C++》:条款38-条款39

条款38通过复合塑模树has-a 或根据某物实现出 条款39明智而审慎的使用private继承...
  • KangRoger
  • KangRoger
  • 2015年03月08日 21:32
  • 1160

effective stl 第19条:理解相等(equality)和等价(equivalence)的区别

#include #include #includeusing namespace std;bool ciStringCompare(const string l, const string r) {...
  • u014110320
  • u014110320
  • 2016年09月20日 23:36
  • 251

Effective STL条款17-条款18

条款17:使用交换技巧来修正过剩容量本节条款告诉我们,如果你有一个vector的容器,容器的容量是10000,但是,现在只用了1,那么为了节省内存,我们应该只保留使用的vector容量,多余的容量应该...
  • u011058765
  • u011058765
  • 2016年04月21日 09:10
  • 291

Effective STL学习笔记-条款19

条款19 了解相等和等价的区别了解相等和等价的区别例如find函数,或者一个set容器插入一个值得时候都会进行比较。但是它们的行为是不同的,find是通过 operator==,而set::inser...
  • gx864102252
  • gx864102252
  • 2017年08月27日 21:01
  • 105

Effective STL 中文版(完整版)

 Winter总算找到《Effective STL》的完整中文版了,奉献给大家。书中作者解释了怎样结合STL组件来在库的设计得到最大的好处。这样的信息允许你对简单、直接的问题开发简单、直接的解决方案,...
  • WinterTree
  • WinterTree
  • 2005年01月16日 01:23
  • 16315

《Effective C++》:条款28-条款29

条款28避免返回handles指向对象内部成分:指的是不能返回对象内部数据/函数的引用、指针等。 条款29为异常安全而努力是值得的:指的是要有异常处理机制,避免发生异常时造成资源泄露等问题。...
  • KangRoger
  • KangRoger
  • 2015年02月19日 19:47
  • 1395

Effective C++ 条款2

尽量以const、enum、inline替换#define首先,大家要明白一个道理。#define是什么,有什么作用。很简单,大家都知道#define实现宏定义,如下代码:#define Flag 1...
  • u011058765
  • u011058765
  • 2015年06月19日 12:06
  • 502

《Effective C++》:条款41-条款42

条款41了解隐式接口和编译期多态 条款42了解typename的双重意义条款
  • KangRoger
  • KangRoger
  • 2015年03月10日 22:13
  • 1247

Effective STL- 熟悉非标准的散列容器(hash 容器)

 条款25:熟悉非标准散列容器STL程序员一般用不了多久就开始惊讶,“vector、list、map,很好,但是散列(hash)表在哪里"?唉,在标准C++库里没有任何散列表。 每个人都同意这是个不幸...
  • bichenggui
  • bichenggui
  • 2009年10月21日 22:21
  • 1914

Effective STL学习笔记-条款39

用纯函数做判断式 判断式是返回bool(或者其他可以隐式转化为bool的东西)。判断式在STL中广泛使用。标准关联容器的比较函数是判断式,判断式函数常常作为参数传递给算法,比如find_if和多种排序...
  • gx864102252
  • gx864102252
  • 2017年12月02日 15:11
  • 17
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Effective STL 条款39
举报原因:
原因补充:

(最多只允许输入30个字)