38.仿函数类与值传递

前言

 
c与c++都不允许把函数作为参数传递给其他函数,所以我们传递给函数的是函数指针。比如下述的qsort声明:

1
void qsort(void *base, size_t nmemb, size_t size,int (*cmpfcn)(const void*, const void*));

函数指针是值传递的。


函数对象

 
STL函数对象是函数指针的一种抽象形式,所以按照惯例,函数对象也是按值传递的,最好的证明就是for_each算法:

1
2
template<class InputIterator,class Function>
Functionfor_each(InputIterator first,InputIterator last,Function f);//pass-by-value

显然,函数对象是一个copy,返回值也是一个copy.
其实函数对象不一定非要是值传递(显式地写为引用传递),但对于STL用户而言,这一条必须遵守,否则某些算法甚至无法编译。

函数对象的特点

既然函数对象是值传递的,那它必须满足两条属性:

  • 足够小,易于copy
  • 单态,否则拷贝时会造成割裂

函数对象的实现

因为不满足上述两条要求就不用仿函数类是愚蠢的,实际上我们有的是办法让大的或者是多态的函数对象以值传递的方式进入STL.
具体方法是:把所需的数据和虚函数从仿函数类中分离出来,放到新的类中;然后在仿函数类中包含一个指针,指向这个新类的对象。
例如,我们需要建立一个包含了大量数据并且使用了多态性的函数子类:

1
2
3
4
5
6
7
8
template<typename T>
class BPFC:public unary_function<T, void> {
private:
Widget w;//存有大量数据
Int x;
public:
virtual void operator()(const T& val) const; //虚函数
};

这样把BPFC作为仿函数类是肯定不行的,正确做法是建立一个包含一个指向实现类的指针的小而单态的类,然后把所有数据和虚函数放到实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T> 
class BPFCImpl:public unary_function<T, void> {
private:
Widget w;
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> {
private:
BPFCImpl<T> *pImpl;
public:
void operator()(const T& val) const{
pImpl->operator() (val);
}
...
};

显然,虽然BPFC是小而单态的,但是其内部的指针在访问时具备了多态性,并且可以访问大量数据。这种技巧在设计模式中被称为Bridge,但我们一定更熟悉另一个名字:PIMPL;


总结

 
函数对象总是通过值传递,因此他们必须小且单态。