分享
 
 
 

Effective STL 条款40

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

条款40:使仿函数类可适配

假设我有一个Widget*指针的list和一个函数来决定这样的指针是否确定一个有趣的Widget:

list<Widget*> widgetPtrs;

bool isInteresting(const Widget *pw);

如果我要在list中找第一个指向有趣的Widget的指针,这很简单:

list<Widget*>::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(),

isInteresting);

if (i != widgetPtrs.end()) {

... // 处理第一个

} // 有趣的指向

// Widget的指针

但如果我想要找第一个指向不有趣的Widget的指针,显而易见的方法却编译失败:

list<Widget*>::iterator i =

find_if(widgetPtrs.begin(), widgetPtrs.end(),

not1(isInteresting)); // 错误!不能编译

取而代之的是,我必须对isInteresting应用ptr_fun在应用not1之前:

list<Widget*>::iterator i =

find_if(widgetPtrs.begin(), widgetPtrs.end(),

not1(ptr_func(isInteresting))); // 没问题

if (i != widgetPtrs.end()) {

... // 处理第一个

} // 指向Widget的指针

那会引出一些问题。为什么我必须在应用not1前对isInteresting应用ptr_fun?ptr_fun为我做了什么,怎么完成上面的工作的?

答案多少有些令人惊讶。ptr_fun做的唯一的事是使一些typedef有效。就是这样。not1需要这些typedef,这就是为什么可以把not1应用于ptr_fun,但不能直接对isInteresting应用not1。因为是低级的函数指针,isInteresting缺乏not1需要的typedef。

not1不是STL中唯一有那些要求的组件。四个标准函数适配器(not1、not2、bind1st和bind2nd)都需要存在某些typedef,一些其他人写的非标准STL兼容的适配器(比如来自SGI和Boost的——参见条款50)也需要。提供这些必要的typedef的函数对象称为可适配的,而缺乏那些typedef的函数对象不可适配。可适配的比不可适配的函数对象可以用于更多的场景,所以只要能做到你就应该使你的函数对象可适配。这不花费你任何东西,而它可以为你仿函数类的客户购买一个便利的世界。

我知道,我知道。我在卖弄,经常提及“某些typedef”而没有告诉你是什么。问题中的typedef是argument_type、first_argument_type、second_argument_type和result_type,但不是那么直截了当,因为不同类型仿函数类需要提供那些名字的不同子集。总的来说,除非你在写你自己的适配器(本书没有覆盖的主题),你才不需要知道任何关于那些typedef的事情。那是因为提供它们的正规方法是从一个基类,或,更精确地说,一个基结构,继承它们。operator()带一个参数的仿函数类,要继承的结构是std::unary_function。operator()带有两个参数的仿函数类,要继承的结构是std::binary_function。

好,简单来说,unary_function和binary_function是模板,所以你不能直接继承它们。取而代之的是,你必须从它们产生的类继承,而那就需要你指定一些类型参数。对于unary_function,你必须指定的是由你的仿函数类的operator()所带的参数的类型和它的返回类型。对于binary_function,你要指定三个类型:你的operator的第一个和第二个参数的类型,和你的operator地返回类型。

这里有两个例子:

template<typename T>

class MeetsThreshold: public std::unary_function<Widget, bool>{

private:

const T threshold;

public:

MeetsThreshold(const T& threshold);

bool operator()(const Widget&) const;

...

};

struct WidgetNameCompare:

std::binary_function<Widget, Widget, bool>{

bool operator()(const Widget& lhs, const Widget& rhs) const;

};

在两种情况下,注意传给unary_function或binary_function的类型与传给仿函数类的operator()和从那儿返回的一样,虽然operator的返回类型作为最后一个参数被传递给unary_function或binary_function有一点古怪。

你可能注意到了MeetsThreshold是一个类,而WidgetNameCompare是一个结构。MeetsThreshold有内部状态(它的阈值数据成员),而类是封装那些信息的合理方法。WidgetNameCompare没有状态,因此不需要任何private的东西。所有东西都是public的仿函数类的作者经常把它们声明为struct而不是class,也许只因为可以避免在基类和operator()函数前面输入“public”。把这样的仿函数声明为class还是struct纯粹是一个个人风格问题。如果你仍然在精炼你的个人风格,想找一些仿效的对象,看看无状态STL自己的仿函数类(比如,less<T>、plus<T>等)一般写为struct。再看看WidgetNameCompare:

struct WidgetNameCompare:

std::binary_function<Widget, Widget, bool> {

bool operator()(cost Widget& lhs, const Widget& rhs) const;

}

虽然operator的参数类型是const Widget&,但传给binary_function的是Widget。一般来说,传给unary_function或binary_function的非指针类型都去掉了const和引用。(不要问为什么。理由不很好也不很有趣。如果你真的想知道,写一些没有去掉它们的程序,然后去解剖编译器诊断结果。如果完成了这步,你仍然对这个问题感兴趣,访问boost.org(参见条款50)然后看看他们关于特性(trait)和函数对象适配器的工作。)

当operator()的参数是指针时这个规则变了。这里有一个和WidgetNameCompare相似的结构,但这个使用Widget*指针:

struct PtrWidgetNameCompare:

std::binary_function<const Widget*, const Widget*, bool> {

bool operator()(const Widget* lhs, const Widget" rhs) const;

};

在这里,传给binary_function的类型和operator()所带的类型一样。用于带有或返回指针的仿函数的一般规则是传给unary_function或binary_function的类型是operator()带有或返回的类型。

不要忘记所有使用这些unary_function和binary_function基类基本理由的冗繁的文字。这些类提供函数对象适配器需要的typedef,所以从那些类继承产生可适配的函数对象。那使我们这么做:

list<Widget> widgets;

...

list<Widget>::reverse_iterator i1 = // 找到最后一个不

find_if(widgets.rbegin(), widgets.rend(), // 适合阈值10的widget

not1(MeetsThreshold<int>(10))); // (不管意味着什么)

Widget w(构造函数参数);

list<Widget>::iterator i2 = // 找到第一个在由

find_if(widgets.begin(), widgets.end(), // WidgetNameCompare定义

bind2nd(WidgetNameCompare(), w); // 的排序顺序上先于w的widget

如果我们没有把仿函数类继承自unary_function或binary_function,这些例子都不能编译,因为not1和bind2nd都只和可适配的函数对象合作。

STL函数对象模仿了C++函数,而一个C++函数只有一套参数类型和一个返回类型。结果,STL暗中假设每个仿函数类只有一个operator()函数,而且这个函数的参数和返回类型要被传给unary_function或binary_function(与我们刚讨论过的引用和指针类型的规则一致)。这意味着,虽然可能很诱人,但你不能通过建立一个单独的含有两个operator()函数的struct试图组合WidgetNameCompare和PtrWidgetNameCompare的功能。如果你那么做了,这个仿函数可能可以和最多一种它的调用形式(你传参数给binary_function的那个)适配,而一个只能一半适配的仿函数可能只比完全不能适配要好。

有时候有必要给一个仿函数类多个调用形式(因此得放弃可适配性),条款7202325给了这种情况的例子。但是那种仿函数类是例外,不是规则。可适配性是重要的,每次你写仿函数类时都应该努力促进它。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有