委托的核心就是封装类成员函数,使不同类的不同函数能够以一种统一的方式被调用
这样进行回调的方式要比使用接口通知模型少写很多代码,避免了重复工作
如果不使用c++11的新特性当然也能自己抡一个类似的轮子(而且还可以随意拓展)
不过有现成没有理由不用
function:函数对象,可以认为这个对应于c#中的委托,不过c++11没有提供事件的抽象,下面就要实现它
bind:创建函数对象的工具
下面的一个类实现了类似c#事件(在c++里面还是叫委托好了)的模板
struct __ut_delegate_id { template<typename T> friend class ut_delegate; private: static int64 s_delageteID; static int64 getID() { return s_delageteID++; } }; template<typename T> class ut_delegate { private: map<int64,T> m_listeners; static int s_Serial; public: int64 add(T func) { int64 id=__ut_delegate_id::getID(); m_listeners.insert(pair<int64,T>( id,func)); return id; } void remove(int64 id) { m_listeners.erase(id); } void NotifyAll() { for_each(m_listeners.begin(),m_listeners.end(),[&](pair<int64,T> func){ func.second(); }); } template<typename T1> void NotifyAll(T1 t1) { for_each(m_listeners.begin(),m_listeners.end(),[&](pair<int64,T> func){ func.second(t1); }); } template<typename T1,typename T2> void NotifyAll(T1 t1,T2 t2) { for_each(m_listeners.begin(),m_listeners.end(),[&](pair<int64,T> func){ func.second(t1,t2); }); } };
使用方法:
void food1(int a) { printf("food1 %d\n",a); } struct fooClass { void fooFunc(int a,int b) { printf("wocao %d %d",a,b); } }; main: ut_delegate<function<void(int)>> del; del.add(std::bind(food1,placeholders::_1)); fooClass* wo1=new fooClass(); del.add(std::bind(&fooClass::fooFunc,wo1,placeholders::_1,555)); del.NotifyAll(123);
缺点是接口不友好,你仍然需要显式使用function和bind(bind很丑陋,虽然很灵活)
这里没有使用+= -= ()之类的操作符,操作符重载看起来很酷,其实经常让人看不懂代码
类里面有一系列的NotifyAll,不过还好未被调用的模板函数不会被编译 所以这个类能正常工作
当然,还有一个老大难问题:如果你使用不正确,编译器会报出令人崩溃的编译错误
还有一个坎:function没有提供==操作符,这给remove带来很大麻烦,但是实际上应该是有方法判断函数对象相等的,因为函数对象实际上就是保存了函数指针和可能的this,
我采用直接比较内存的方法判断相等,虽然是个hack,但是能工作,完全fix这个问题需要研究代码,或者进一步测试
看了function的代码之后发现实际上function没办法提供这个功能,但是我们可以想个曲线救国的办法,绑定的时候返回一个id,外部释放的时候使用这个id来处理
但是我们仍然需要考虑生存期问题,假设现在有委托A,类B把自己的成员函数绑定到A上,那么B销毁时需要去调用remove,那么B需要持有A的指针,但是如果此时A已经销毁,所以A在销毁时需要通知B
也就是说A,B各自在销毁的时候都要去通知对方,这样又增加了很多复杂度,如果能自动管理生存期就好了!
但是如果A始终能先于B销毁就没有这个问题了
话说回来,果然还是自己抡的东西好用..