std::vector<Employee> emps; std::for_each(emps.begin(), emps.end(), std::mem_fun_ref(&Employee::DoStandardRaise));
The _ref bit at the end of the name mem_fun_ref is a bit of an historical oddity. When writing code like this, you should just remember to say mem_fun_ref if the container is a plain old container of objects, because for_each will be operating on references to those objects, and to say mem_fun if it's a container of pointers to objects:
std::vector<Employee*> emp_ptrs; std::for_each(emp_ptrs.begin(), emp_ptrs.end(), std::mem_fun(&Employee::DoStandardRaise));
You'll probably have noticed that, for clarity, I've been showing how to do this with functions that take no parameters. You can use the bind… helpers to deal with some functions that take an argument, and the principle is the same. Unfortunately you can't use this approach for functions that take two or more arguments. Still, it can be useful.
find_if(widgetPtrs.begin(), widgetPtrs.end(),
not1(ptr_func(isInteresting)));
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的那个)适配,而一个只能一半适配的仿函数可能只比完全不能适配要好。