细说C++委托和消息反馈模板(1)(3)
桥式委托的进一步研究
看过上面的桥式委托之后,可能会有点怀疑他的性能,需要一个interface指针一个functor类/函数指针,调用的时候需要一次查vtable,然后再一次做operator()调用。其实,这些消耗都不算很大的,整个桥式委托的类结构是简单的,相对于前面说的继承整个类之类的做法开销还是比较小的,而且又比函数指针通用而且类型安全。最重要的是,刚才的Signal可以方便地改写为Multi-Cast Delegation即一个信号引发多个响应——把Singal内部的DelegationInterface*指针改为一个指针队列就可以了。
不过,我们刚才实现的桥式委托只能接收函数指针和functor,不能接收另外一个类的成员函数,有时候这是非常有用的动作。比如设置一个按钮Button的OnClick事件的响应为一个MsgBox的Show方法。当然,MsgBox还有其他非常多的方法,这样就可以不用局限于把MsgBox当成一个functor了。
我们要改写刚才的整个桥来实现这个功能,在这里需要你对指向成员函数得指针有所了解。
- // 新版的桥式委托,可以接收类的成员函数作为响应
- struct DelegationInterface {
- virtual ~DelegationInterface() {};
- virtual void Run() = 0;
- };
- template<class T>
- struct DelegationImpl : public DelegationInterface {
- typedef void (T::* _pF_t)(); // 指向类T成员函数的指针类型
- DelegationImpl(T* _PP, _pF_t pF) :_P(_PP), _PF(pF) {}
- virtual void Run() {
- if(_P) { (_P->*_PF)(); } // 成员函数调用,很别扭的写法(_P->*_PF)();
- }
- T* _P; // Receiver类
- _pF_t _PF; // 指向Receiver类的某个成员函数
- };
- struct Signal
- {
- DelegationInterface* _PI;
- Signal() :_PI(NULL) {}
- void operator() () { if(_PI) _PI->Run(); }
- // 新的ConnectSlot需要指定一个类以及这个类的某个成员函数
- template<class T>
- void ConnectSlot(T& recv, void (T::* pF)()) { // pF这个参数真够别扭的
- _PI = new DelegationImpl<T>(&recv, pF);
- }
- };
注意:ConnectSlot方法的pF参数类型非常复杂,也可以简化如下,即把这个类型检测推到DelegationImpl类去完成,而不在Connect这里进行么?编译器可以正确识别。对于模板来说,很多复杂的参数类型都可以用一个简单的类型代替,不用关心细节,就象上面用一个F代替void (T::*)()。有时候能改善可读性,有时候象反。
- template<class T, class F>
- void ConnectSlot( T& recv, F pF ) {
- PI_ = new DelegationImpl<T>(&recv,pF);
- }
这个新版怎么用呢,很简单的。比如你的MsgBox类有一个成员函数Show,你可以把这个作为响应函数:
- MsgBox box;
- Socket x; // Socket还跟旧的版本一样
- x.OnRecv.ConnectSlot(box, &MsgBox::Show);
注意上面这里引用成员函数指针的写法,一定不能写成box.Show,呵呵,希望你还记得成员函数是属于类公共的东西,不是某个实例的私有产品。大家不妨进一步动一下脑筋,把新版的Signal和旧版的Signal结合一下,你就可以获得一个功能超强的Delegation系统了。
点评:用signal的办法确实可以方便地动态替换处理函数,不过这是以每个可能被处理的消息都要在每个对象中占用一个 signal 的空间为代价的。而且,需要动态改变处理函数的应用我已经不记得什么时候见过了。即使有,也可以通过在override的virtual函数里自己处理实现,虽说麻烦,但也是可能的。此外,以上代码并不够规范,下划线加大写字母开头的标识符是保留给语言的实现用的。
- 上一篇:C++内存管理的探讨(1)
- 下一篇:解析C++和C的区别