[6 函数子类及函数] 38. 遵循按值传递的原则来设计函数子类(桥接模式)

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(桥接模式)。

参考网址:

http://c.biancheng.net/view/1364.html

Supongo que te gusta

Origin blog.csdn.net/u012906122/article/details/119780458
Recomendado
Clasificación