函数对象在算法、容器方面用的很普遍,这部分作者给出了5个Item,着重强调如何以STL方式来使用这些函数对象。
Item38:Design functor classes for pass-by-value.
c/c++将函数作为参数传递时,传递的是函数指针,如我们看qsort的声明:
void qsort(void *base, size_t nmemb, size_t size,
int (*cmpfcn)(const void*, const void*));
函数参数cmpfcn将函数体拷贝(按值传递)给函数sqort。STL的约定是传给函数和从函数返回时函数对象是按值传递的。如果通过引用传递和返回,那么编译器可能不能通过,如下面我们对for_each指定显式模板实参:
for_each<DequeIntIter, DoSomething&>(di.begin(), di.end(), d);
按值传递意味着我们的函数对象需要满足下两个条件:
1、函数对象精炼,较少拷贝花费。
2、非多态。若是多态那么在传递派生类的时候可能会发生派生类部分被切割。
当有时候无法避免的时,我们需要将数据和多态移到另一个类中,然后给你的函数对象一个指向这个新类的指针,如考虑下面这个例子:
template<typename T>
class BPFC : public unary_function<T, void> { // BPFC = “Big Polymorphic
private:
Widget w; // too many members
Int x;
...
public:
virtual void operator()(const T& val) const; // virtual function,
... // cut off!
};
建立一个包含一个指向实现类的指针的小而单态的类,然后把所有数据和虚函数放到实现类:
template<typename T>
class BPFCImpl
public unary_function<T, void> {
private:
Widget w; // all members
int x; //
...
virtual ~BPFCImpl();
virtual void operator()(const T& val) const;
friend class BPFC<T>;
};
template<typename T>
class BPFC : public unary_function<T, void> { // monomorphic
private:
BPFCImpl<T> *pImpl; // pointe to BPFCImpl
public:
void operator()(const T& val) const // nonvirtual
{
pImpl->operator() (val);
}
...
};
上面的实现方式就是所谓的”Bridge模式“。由于上诉函数对象含有指针类型,所以必须考虑复制、拷贝、析构函数做了正确的事情。
Item39:Make predicates pure functions.
纯函数是返回值只依赖参数的函数,如果predicate不是纯函数,那么它在算法中起到的作用可能和我们想的不一样。如考虑下面这个predicate class,它在第三次被调用的时候返回true:
class BadPredicate : public unary_function<Widget, bool> {
public:
BadPredicate(): timesCalled(0) {}
bool operator()(const Widget&)
{
return ++timesCalled == 3;
}
private:
size_t timesCalled;
};
我们将它应用于vector:
vector<Widget> vw;
vw.erase(remove_if(vw.begin(), vw.end(), BadPredicate()),
vw.end()); remove the third Widget;
这段代码表面上看没有问题,但是很多STL实现不仅删去第三个对象,还会删去第六个对象!我们来分析一下可能的一种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的timesCalled初始化为0,然后
按值传递
给find_if,在find_if中执行了三次,控制权转移到remove_copy_if,而此也是将p拷贝给它,
而timeCalled仍为0,在remove_copy_if第三次调用predicate时也会返回ture。
所以我们必须将predicate设计成纯函数,若是在函数对象中,operator()必须是const的。
Item 40. Make functor classes adaptable.
标准函数适配器(not1、not2、bind1st和bind2nd)需要某些typedef(argument_type、first_argument_type、second_argument_type和result_type),我们传递函数给他们时必须让其可适配。考虑如下代码:
bool isInteresting(const Widget *pw);
list<Widget*>::iterator i = find_if(widgetPtrs.begin(),
widgetPtrs.end(), not1(ptr_func(isInteresting)));
为了让isInteresting可适配,我们在前面加上ptr_func。
对于函数对象,我们通常根据实参个数继承std::unary_function或者std::binary_function。他们都是类模板,我们要指定参数实例化他们才能继承,除了相应的参数,我们还要指定返回类型,如下面的代码:
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;
...
};
一般来说传递给他们的非指针类型都是非const非引用的,带有或返回指针的仿函数的一般规则是传给unary_function或binary_function operator()带有或返回的类型。最后确保operator()没有被重载,意外的情况会让其失去可适配性。
Item41:Understand the reasons for ptr_fun, mem_fun, and mem_fun_ref.
STL中函数和函数对象总使用用于非成员函数的语法形式调用,当我们的一类predicate为一个类的成员函数时,我们就需要使用这些来将其转换成函数对象让算法调用。如下面代码:
list<Widget*> lpw;
...
for_each(lpw.begin(), lpw.end(), mem_fun(&Widget::test));
list<Widget> lpw;
...
for_each(lpw.begin(), lpw.end(), mem_fun_ref(&Widget::test));
同时,mem_fun和mem_fun_ref都提供了一些typedef。
当一个成员函数传递给STL组件时,必须使用mem_fun或者mem_fun_ref,对于非成员函数当我们不确定是否需要ptr_fun是,总使用ptr_fun,这样子没有开销。
Item42:Make sure less<T> means operator<.
假设一个Widget有最高速度和重量,我们想建立一个按照最高速度排序Widget的multiset<Widget>。multiset<Widget>的默认比较函数是less<Widget>,而且默认的less<Widget>通过调用Widget的operator<来工作,一种实现方式是通过特化less<T>:
template<> struct std::less<Widget> // specialization
: public std::binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const
{
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
这样的设计缺乏合理性,通常来说,修改std里的组件是禁止的。
operator<不仅是实现less的默认方式,它还是程序员希望less做的,上面的问题我们可以建立一个functor class来实现如下:
struct MaxSpeedCompare:
public binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const
{
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
multiset<Widget, MaxSpeedCompare> widgets;
而multiset<Widget>是按默认less<Widget>来排序的,也就是使用operator<。