Effective C++ 条款35 考虑virtual函数以外的其他选择
1. 在一个继承层次中,不同的类要实现同一接口的不同实现,最先想到的可能是虚函数,假设一个存在一个继承体系,这个集成体系中的每一层都需要一个名为fun函数,那么可能会像这样实现:
clase Base{ public: ... virtual fun(int num){...} private: ... } class Derived:public Base{ public: ... virtual fun(int num){} private: ... }
但除了将fun设为虚函数,还有其他选择.
2. "藉由Non-Virtual Interface手法实现Template Method模式"
这一方法的基本思想是"令客户通过public non-virtual成员函数间接调用private virtual函数,成为non-virtual interface(NVI)手法",它是所谓Template Method设计模式(和template无关)的一个独特表现形式.即:
class Base{ public: ... void wrapper(int num){ ... //做一些事前工作 fun(num); ... //做一些事后工作 } private: virtual void fun(int num){...} }; class Derived:public Base{ public: ... private: virtual void fun(int num){...} };
通过wrapper函数间接调用虚函数fun的优点在于在wraper函数可以在虚函数fun被调用之前做一些准备工作,在fun被调用之后做一些事后工作(正如注释)
但以上代码将fun设为private,因此取消了Derived继承Base的fun的可能性,因此可以将fun设为protected(如果设为public将不能实施NVI手法)
3. "藉由Function Pointers是吸纳Strategy模式"
NVI手法只是对virtual函数做了一下修饰,加了一层wrapper函数,另一种替代方案是为继承层次中每一个类加一个函数指针,指向所需的函数,即:
class Base{ public: typedef void (*PtrF)(int); ... Base(PtrF para):ptr(para){...} private: PtrF ptr; } class Derived:public Base{ public ... Derived(ptrF para):p(para){...} private: ... }
这样做提供了更大弹性:同一类型不同实体可以调用不同函数;同一实体可调用的函数可以在运行时期变更.
缺点就是p所指向的函数不再是成员函数,如果想要访问类的non-public部分就只能弱化类的封装:将函数设为friend或将使用到的non-public类成员设为public或提供public接口获取non-public成员.
4. "藉由tr1::function完成Strategy模式"
3中策略提供了一定弹性,但不一定要使用指针,使用<functional>中的function类模板可以获得更大弹性(function模板的介绍见条款54),即:
Base{ public: function<void(int)> ptr; ... private: ... } Derived:public Base{ public: ... private: ... }
此策略与3相比较的优点在于,ptr"可持有任何可调用物(callable entity,也就是函数指针,函数对象,或成员函数指针),只要其签名式兼容于需求端",因此提供了比3更大的弹性.ptr的方法像这样:
void fun(int num){...} ptr=&fun;
正如以上所言,ptr可以指向非成员函数,static成员函数,也可以指向函数对象,也可用函数指针对其赋值,但要使它指向成员函数还需要其他操作,因为成员函数有默认的this指针作为第一个参数,使用同一头文件中的bind函数模板可以解决这一问题,bind函数模板返回一个函数指针,bind的函数模板原型像这样:
template <class Fn, class... Args> bind (Fn&& fn,Args&&... args);
其中第一个参数是函数指针,接下来的参数是函数所需要的参数,如果参数是确定的值,那么bind返回的函数指针的同一位置的参数设为那个值,如果参数要保留,那么使用占位符tr1::placeholders::_1,tr1::placeholders::_2 ... tr1::placeholders::_N,即:
class Demo{ public: int fun(int){...} ... private: ... }
Demo tmp;
ptr=bind(&Demo::fun,tmp,tr1::placeholders::_1);
这样就把ptr绑定到成员函数了.
关于function类模板和bind函数模板的具体使用方法见:
http://blog.csdn.net/xiucaijiang/article/details/5999441
http://www.cplusplus.com/reference/functional/bind/?kw=bind
5. ”古典的Strategy模式"
传统的Strategy做法会将fun做成一个分离的继承体系中的虚函数,即:
class Fun{ public: virtual void fun(int num){...} ... private: ... } class Base{ public: Base(Fun*para):ptr(para){...} void fun(){ptr->fun();} ... private: Fun* ptr; ... }
这种策略的优点在于它提供了将一个既有的fun函数纳入使用的可能性,只要为Fun类集成体系新添加一个derived class即可.