分享
 
 
 

Effective STL 条款39

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

条款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; // 判断式应该是纯函数,

} // 纯函数没有状态

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有