免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: haoji
打印 上一主题 下一主题

《Effective STL》 [复制链接]

论坛徽章:
0
171 [报告]
发表于 2008-05-31 16:37 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款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的指针

论坛徽章:
0
172 [报告]
发表于 2008-05-31 16:38 |只看该作者
那会引出一些问题。为什么我必须在应用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;
...
};

论坛徽章:
0
173 [报告]
发表于 2008-05-31 16:38 |只看该作者
struct WidgetNameCompare:
public 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:
public 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)然后看看他们关于特性和函数对象适配器的工作。)
当operator()的参数是指针时这个规则变了。这里有一个和WidgetNameCompare相似的结构,但这个使用
Widget*指针:
struct PtrWidgetNameCompare:
public 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基类基本理由的冗繁的文字。这些类提供函数对象适配

论坛徽章:
0
174 [报告]
发表于 2008-05-31 16:39 |只看该作者
器需要的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的那个)适配,而一个
只能一半适配的仿函数可能只比完全不能适配要好。
有时候有必要给一个仿函数类多个调用形式(因此得放弃可适配性),条款7、20、23和25给了这种情况的例
子。但是那种仿函数类是例外,不是规则。可适配性是重要的,每次你写仿函数类时都应该努力促进它。

论坛徽章:
0
175 [报告]
发表于 2008-05-31 16:40 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因
ptr_fun/mem_fun/mem_fun_ref系列是什么意思的?有时候你必须使用这些函数,有时候不用,总之,它们是
做什么的?它们似乎只是坐在那里,没用地挂在函数名周围就像不合身的外衣。它们不好输入,影响阅读,
而且难以理解。这些东西是STL古董的附加例子(正如在条款10和18中描述的那样),或者只是一些标准委员
会的成员因为太闲和扭曲的幽默感而强加给我们的语法玩笑?
冷静一下。虽然ptr_fun、mem_fun和mem_fun_ref的名字不太好听,但它们做了很重要的工作,而不是语法玩
笑,这些函数的主要任务之一是覆盖C++固有的语法矛盾之一。
如果我有一个函数f和一个对象x,我希望在x上调用f,而且我在x的成员函数之外。C++给我三种不同的语法
来实现这个调用:
f(x); // 语法#1:当f是一个非成员函数
x.f(); // 语法#2:当f是一个成员函数
// 而且x是一个对象或一个对象的引用
p->f(); // 语法#3:当f是一个成员函数
// 而且p是一个对象的指针
现在,假设我有一个可以测试Widget的函数,
void test(Widget& w); // 测试w,如果没通过
// 就标记为“failed”
而且我有一个Widget的容器:
vector<Widget> vw; // vw容纳Widget
要测试vw中的每个Widget,我很显然可以这么使用for_each:
for_each(vw.begin(), vw.end(), test); // 调用#1(可以编译)

论坛徽章:
0
176 [报告]
发表于 2008-05-31 16:40 |只看该作者
但想象test是一个Widget的成员函数而不是一个非成员函数,也就是说,Widget支持自我测试:
class Widget {
public:
void test(); // 进行自我测试;如果没通过
// 就把*this标记为“failed”
};
在一个完美的世界,我也将能使用for_each对vw中的每个对象调用Widget::test:
for_each(vw.begin(), vw.end(),
&Widget::test); // 调用#2(不能编译)
实际上,如果世界真的完美,我将也可以使用for_each来在Widget*指针的容器上调用Widget::test:
list<Widget*> lpw; // lpw容纳Widget的指针
for_each(lpw.begin(), lpw.end(),
&Widget::test); // 调用#3(也不能编译)
但是想想在这个完美的世界里必须发生的。在调用#1的for_each函数内部,我们以一个对象为参数调用一个非
成员函数,因此我们必须使用语法#1。在调用#2的for_each函数内部,我们必须使用语法#2,因为我们有一个
对象和一个成员函数。而调用#3的for_each函数内部,我们需要使用语法#3,因为我们将面对一个成员函数和
一个对象的指针。因此我们需要三个不同版本的for_each,而那个世界将有多完美?
在我们拥有的世界上,我们只有一个版本的for_each。想一种实现不难:
template<typename InputIterator, typename Function>
Function for_each(InputIterator begin, InputIterator end, Function f)
{
while (begin != end) f(*begin++);
}
这里,我强调当调用时for_each是用语法#1这个事实。这是STL里的一个普遍习惯:函数和函数对象总使用用
于非成员函数的语法形式调用。这解释了为什么调用#1可以编译而调用#2和#3不。这是因为STL算法(包括
for_each)牢牢地绑定在句法#1上,而且只有调用#1与那语法兼容。

论坛徽章:
0
177 [报告]
发表于 2008-05-31 16:41 |只看该作者
也许现在清楚为什么mem_fun和mem_fun_ref存在了。它们让成员函数(通常必须使用句法#2或者#3来调用
的)使用句法1调用。
mem_fun和mem_fun_ref完成这个的方式很简单,虽然如果你看一下这些函数之一的声明会稍微清楚些。它们
是真的函数模板,而且存在mem_fun和mem_fun_ref模板的几个变体,对应于它们适配的不同的参数个数和常
量性(或缺乏)的成员函数。看一个声明就足以理解事情怎样形成整体的:
template<typename R, typename C> // 用于不带参数的non-const成员函数
mem_fun_t<R,C> // 的mem_fun声明。
mem_fun(R(C::*pmf)()); // C是类,R是被指向
// 的成员函数的返回类型
mem_fun带有一个到成员函数的指针,pmf,并返回一个mem_fun_t类型的对象。这是一个仿函数类,容纳成
员函数指针并提供一个operator(),它调用指向在传给operator()的对象上的成员函数。例如,在这段代码中,
list<Widget*> lpw; // 同上
...
for_each(lpw.begin(), lpw.end(),
mem_fun(&Widget::test)); // 这个现在可以编译了
for_each接受一个mem_fun_t类型的对象,持有一个Widget::test的指针。对于在lpw里的每个Widget*指针,
for_each使用语法#1“调用”mem_func_t,而那个对象立刻在Widget*指针上使用句法#3调用Widget::test。
总的来说,mem_fun适配语法#3——也就是当和Widget*指针配合时Widget::test要求的——到语法1,也就是
for_each用的。因此也不奇怪像mem_fun_t这样的类被称为函数对象适配器。知道这个不应该使你惊讶,完全
类似上述的,mem_fun_ref函数适配语法#2到语法#1,并产生mem_fun_ref_t类型的适配器对象。
mem_fun和mem_fun_ref产生的对象不仅允许STL组件假设所有函数都使用单一的语法调用。它们也提供重要
的typedef,正如ptr_fun产生的对象一样。条款40告诉了你这些typedef后面的故事,所以我将不在这里重复
它。但是,这使我们能够理解为什么这个调用可以编译。
for_each(vw.begin(), vw.end(), test); // 同上,调用#1;
// 这个可以编译
而这些不能:

论坛徽章:
0
178 [报告]
发表于 2008-05-31 16:41 |只看该作者
for_each(vw.begin(), vw.end(), &Widget::test); // 同上,调用#2;
// 不能编译
for_each(lpw.begin(), lpw.end(), &Widget::test); // 同上,调用#3;
// 不能编译
第一个调用(调用#1)传递一个真的函数,因此用于for_each时不需要适配它的调用语法;这个算法将固有地
使用适当的语法调用它。而且,for_each没有使用ptr_fun增加的typedef,所以当把test传给for_each时不必使用
ptr_fun。另一方面,增加的typedef不会造成任何损伤,所以这和上面的调用做相同的事情:
for_each(vw.begin(), vw.end(), ptr_fun(test)); // 可以编译,行为
// 就像上面的调用#1
如果你关于什么时候使用ptr_fun什么时候不使用而感到困惑,那就考虑每当你传递一个函数给STL组件时都
使用它。STL将不在乎, 并且没有运行期的惩罚。可能出现的最坏的情况就是一些读你代码的人当看见不必
要的ptr_fun使用时,可能会扬起眉毛。我认为,那有多让你操心依赖于你对扬起眉毛的敏感性。
一个与ptr_fun有关的可选策略是只有当你被迫时才使用它。如果当typedef是必要时你忽略了它,你的编译器
将退回你的代码。然后你得返回去添加它。
mem_fun和mem_fun_ref的情况则完全不同。只要你传一个成员函数给STL组件,你就必须使用它们,因为,
除了增加typedef(可能是或可能不是必须的)之外,它们把调用语法从一个通常用于成员函数的适配到在
STL中到处使用的。当传递成员函数指针时如果你不使用它们,你的代码将永远不能编译。
现在只留下成员函数适配器的名字,而在这里,最后,我们有一个真实的STL历史产物。当对这些种适配器
的需求开始变得明显时,建立STL的人们正专注于指针的容器。(这种容器的缺点在条款7、20和33描述,这
看起来可能很惊人,但是记住指针的容器支持多态,而对象的容器不支持。)他们需要用于成员函数的适配
器,所以他们选择了mem_fun。但很快他们意识到需要一个用于对象的容器的另一个适配器,所以他们使用
了mem_fun_ref。是的,它非常不优雅,但是这些事情发生了。告诉我你从未给你的任何组件一个你过后意识
到,呃,很难概括的名字

论坛徽章:
0
179 [报告]
发表于 2008-05-31 16:42 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款42:确定less<T>表示operator<
正如所有了解零件(Widget)的人所知道的,Widget有重量和最高速度:
class Widget {
public:
...
size_t weight() const;
size_t maxSpeed() const;
...
};
此外众所周知的是给Widget排序的自然方法是按重量。用于Widget的operator<表现成这样:
bool operator<(const Widget& lhs, const Widget& rhs)
{
return lhs.weight() < rhs.weight();
}
但是假设我们想建立一个按照最高速度排序Widget的multiset<Widget>。我们知道multiset<Widget>的默认比较
函数是less<Widget>,而且我们知道默认的less<Widget>通过调用Widget的operator<来工作。鉴于这种情况,
好像得到以最高速度排序的multiset<Widget>很明显一种方法是通过特化less<Widget>来切断less<Widget>和
operator<之间的纽带,让它只关注Widget的最高速度:
template<> // 这是一个std::less
struct std::less<Widget>: // 的Widget的特化;
public // 也是非常坏的主意
std::binary_function<Widget,
Widget, // 关于这个基类更多
bool> { // 的信息参见条款40
bool operator()(const Widget& lhs, const Widget& rhs) const
{

论坛徽章:
0
180 [报告]
发表于 2008-05-31 16:42 |只看该作者
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
这看起来既有欠考虑又确实有欠考虑,但你想到的理由可能不欠考虑。你是不是很奇怪它完全可以编译?很
多程序员指出上述不只是一个模板的特化,而且是在特化std namespace中的模板。“std不应该是神圣的,为
库的实现保留,而且超出了一般程序员可以达到的范围?”他们问,“编译器不该拒绝这个干预C++不朽的
运转的尝试吗?”他们很奇怪。
通常,试图修改std里的组件确实是禁止的(而且这么做通常被认为是行为未定义的范畴),但是在一些情况
下,修补是允许的。具体来说,程序员被允许用自定义类型特化std内的模板。特化std模板的选择几乎总是更
为优先,但很少发生,这确实合理的。例如,智能指针类的作者经常想让他们的类在排序的时候行为表现得
像内建指针,因此用于智能指针类型的std::less特化并不罕见。例如下面内容,是Boost库的shared_ptr的一部
分,你可以在条款7和50中获悉智能指针:
namespace std {
template<typename T> // 这是一个用于boost::shared_ptr<T>
struct less<boost::shared_ptr<T> >: // 的std::less的特化
public // (boost是一个namespace)
binary function<boost::shared_ptr<T>,
boost::shared_ptr<T>,// 这是惯例的
bool> { // 基类(参见条款40)
bool operator()(const boost::shared_ptr<T>& a,
const boost::shared_ptr<T>& b) const
{
return less<T*>()(a.get(),b.get()); // shared_ptr::get返回
} // shared_ptr对象内的
// 内建指针
};
}
这不过分,当然也没有什么奇怪的,因为这个less的特化仅仅在排序上保证智能指针的行为与它们的内建兄弟
相同。哎,我们试验性的用于Widget的less特化却比较奇怪。
C++程序员可以被原谅存在某些假设。例如,他们假设拷贝构造函数拷贝。(就像条款8证明的,没有依照这
个约定可能导致惊人的行为。)他们假设对一个对象取地址就会产生一个指向那个物体的指针。(转向条款
18去了解当这不为真时将发生什么。)他们假设像bind1st和not2这样的适配器可能应用于函数对象。(条款40
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP