C或C++都不允许将函数作为参数传递,只能传递函数指针,以标准库中的qsort为例:
void qsort(void* base, size_t nmemb, size_t size,
int(*cmpfcn)(const void*, const void*));
第46条会解释通常情况下sort算法优于qsort函数。cmpfcn这个函数指针采用了按值传递的方式。
STL中函数对象在函数之间的传递也是按值传递的。以for_each算法为例,它需要一个函数对象作为参数,同时其返回值也是一个函数对象,都是按值传递的:
template<class InputIterator, class Function>
// 按值返回 return-by-value
Function for_each(InputIterator first, InputIterator last,
Function f); // 按值传递 pass-by-value
你可以迫使for_each按照引用方式传递参数和返回:
for_each<ClassA, Func&>(...);
但是STL使用者几乎不会这么做,因为将函数对象按照引用来传递时,有些STL算法根本不能通过编译,以not1配接器为例:
template <class Predicate>
unary_negate<Predicate>
not1(const Predicate& pred);
如果判别式Predicate是引用形式,则编译报错,因为C++不支持"引用的引用"。
所以,函数对象往往是按值传递和返回的,这意味着:
(1)函数对象必须尽可能小,否则复制的开销大
(2)函数对象必须是单态的,不能是多态的,即不能使用虚函数,否则会产生剥离问题(第3条)。
但实际上,不是所有的函数对象都小巧且单态。所以,需要找到一种方法:既允许函数对象可以很大且多态,也可以与STL采用的按值传递习惯保持一致。
方法是:将所需的数据和虚函数从函数子类中分离出来,放到一个新的类中;然后在函数子类中包含一个指针,指向这个新类的对象。
举例如下,一个包含大量数据且使用了多态的函数子类:
// BPFC:"Big Polymorphic Functor Class" 大的多态的函数子类
template<typename T>
class BPFC: public unary_function<T, void> {
private:
Widget w;
int x;
... //包含大量数据
public:
virtual void operator()(const T& val) const; //虚函数,存在剥离问题
...
};
改写为创建一个小巧的、单态的类,其中包含一个指针,指向另一个实现类,将所有数据和虚函数都放在实现类里:
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>; //允许BPFC访问内部数据
};
template<typename T>
// BPFC类:短小,单态
class BPFC: public unary_function<T, void> {
private:
BPFCImpl<T> *pImpl;
public:
void operator()(const T& val) const
{
pImpl->operator()(val);
}
...
};
这个技术在C++比较常用,《Effective C++》的第34条,《Design Patterns》的Bridge Pattern(桥接模式)。
参考网址: