C++實現委托機制(一) 1.引言: 如果你接觸過C#,你就會覺得C#中的delegate(委托)十分靈巧,它的用法上和C\C++的函數指針很像,但是卻又比C\C++的函數指針更加靈活。並且委托可以一對多,也就是可以注冊多個函數,甚至是某個類的非靜態成員函數。而實現事件消息機制【1】也十分依賴於委托機制。基於這樣的目的,我們試着在C++上封裝出這樣的一個委托機制。 【1】值得注意的是這里的委托事件模式與Windows的消息循環體系是不同的,通常Windows的消息是放到消息隊列中,應用程序進程從隊列中得到消息,然后調用消息處理過程來處理消息,這里是真正的消息通知,並且消息處理過程是有固定的函數聲明的,不能更改成其他的格式,但是委托事件模式實際上就是一次函數調用,委托事件模式的使用,其好處是在開發中可以像真正的消息事件體系一樣來理解整個體系模式,可以做到很好的接口分離。 2.委托功能使用: 委托使用簡單,支持多播,可以添加刪除委托。同時支持C++的普通函數、模板函數、類成員函數,類的靜態成員函數,並且支持多態。 我們來看一個簡單的例子: #include "MyDelegate.h" using namespace Delegate; void NormalFunc(int a) { printf("這里是普通函數 :%d\n", a); } class A { public: static void StaticFunc(int a) { printf("這里是成員靜態函數 : %d\n", a); } void MemberFunc(int a) { printf("這里是成員非靜態函數 : %d\n", a); } }; int _tmain(int argc, _TCHAR* argv[]) { //首先創建了一個返回值為 void ,參數為int 的一個委托。 CMultiDelegate<void, int> e; //將三個函數注冊到該委托中 e += newDelegate(NormalFunc); e += newDelegate(A::StaticFunc); e += newDelegate(&A(), &A::MemberFunc); //調用 e(1); return 0; } 運行結果: 這里是普通函數 :1 這里是成員靜態函數 : 1 這里是成員非靜態函數 : 1 由此可以看到將三個函數注冊到委托中后,調用委托不僅三個函數不僅能夠成功調用,而且參數也是成功傳遞的。 3.實現無返回值無參數委托的構造 這一部分代碼是參照http://blog.csdn.net/gouki04/article/details/6852394這篇博客上寫的。 我們先來看C++中普通函數指針和成員函數指針的區別: void NormalFunc() { printf("這里是普通函數\n"); } class A { public: static void StaticFunc() { printf("這里是成員靜態函數\n"); } void MemberFunc() { printf("這里是成員非靜態函數\n"); } }; int _tmain(int argc, _TCHAR* argv[]) { //普通函數 typedef void(*NormalFuncp)(); //成員函數 typedef void(A::*MemberFuncp)(); NormalFuncp fun1 = NormalFunc; MemberFuncp fun2 = &A::MemberFunc; NormalFuncp fun3 = A::StaticFunc; A a; fun1(); (a.*fun2)(); fun3(); return 0; } 可以看到普通函數指針調用函數的方式和成員非靜態函數指針調用函數的方式不同,成員非靜態函數指針調用函數需要依賴於該類的一個對象,並且用 .* 或者 ->* 的語法來調用。而成員靜態函數調用方式卻和普通函數差不多。所以我們需要創建一個委托的基本接口對於不同類型指針的再來派生多態處理。 class IDelegate { public: virtual ~IDelegate() { } virtual bool isType(const std::type_info& _type) = 0; virtual void invoke() = 0; virtual bool compare(IDelegate *_delegate) const = 0; }; 這里定義了三個接口,一個是調用,表示調用該Delegate對應的函數指針指向的函數。剩下兩個是類型判斷,使用了C++的RTTI,動態類型的判斷。 接下來我們來派生出能注冊普通函數的委托。 class CStaticDelegate : public IDelegate { public: typedef void (*Func)(); CStaticDelegate(Func _func) : mFunc(_func) { } virtual bool isType(const std::type_info& _type) { return typeid(CStaticDelegate) == _type; } virtual void invoke() { mFunc(); } virtual bool compare(IDelegate *_delegate) const { if (0 == _delegate || !_delegate->isType(typeid(CStaticDelegate)) ) return false; CStaticDelegate * cast = static_cast<CStaticDelegate*>(_delegate); return cast->mFunc == mFunc; } private: Func mFunc; }; 然后是可以注冊指向成員非靜態函數的指針的委托,因為指向成員非靜態函數的類別是這樣的 void (ClassName::*FuncName)();而ClassName又是不確定的所以我們這里要使用模板類來封裝: template<class T> class CMethodDelegate : public IDelegate { public: typedef void (T::*Method)(); CMethodDelegate(T * _object, Method _method) : mObject(_object), mMethod(_method) { } virtual bool isType( const std::type_info& _type) { return typeid(CMethodDelegate<T>) == _type; } virtual void invoke() { (mObject->*mMethod)(); } virtual bool compare(IDelegate *_delegate) const { if (0 == _delegate || !_delegate->isType(typeid(CMethodDelegate<T>))) return false; CMethodDelegate<T>* cast = static_cast<CMethodDelegate<T>*>(_delegate); return cast->mObject == mObject && cast->mMethod == mMethod; } private: T * mObject; Method mMethod; }; 這里的類型T是指這個委托注冊的成員函數指針所屬的類的類別。比如我注冊 A::&MemberFunc ,那么這里的T就被替換為A. 其實大家仔細看代碼可以發現這兩個類十分相似只是invoke() 里面調用的方式不同。還有這里的compare判斷是指看兩個委托指向的成員函數和對象是否一樣,如果只是成員函數一樣,綁定的對象不一樣也視作不同的委托。 這樣我們就把C++中的無返回值、無參數的普通函數指針、成員函數指針封裝好了。 最后提供統一的接口去生成”函數指針對象“ inline IDelegate* newDelegate( void (*_func)() ) { return new CStaticDelegate(_func); } template<class T> inline IDelegate* newDelegate( T * _object, void (T::*_method)() ) { return new CMethodDelegate<T>(_object, _method); } 最后我們我們實現委托,這里我們對多個函數指針的存儲使用了STL的list.所以頭文件中需要引入<list> class CMultiDelegate { public: typedef std::list<IDelegate*> ListDelegate; typedef ListDelegate::iterator ListDelegateIterator; typedef ListDelegate::const_iterator ConstListDelegateIterator; CMultiDelegate () { } ~CMultiDelegate () { clear(); } bool empty() const { for (ConstListDelegateIterator iter = mListDelegates.begin(); iter!=mListDelegates.end(); ++iter) { if (*iter) return false; } return true; } void clear() { for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter) { if (*iter) { delete (*iter); (*iter) = 0; } } } CMultiDelegate& operator+=(IDelegate* _delegate) { for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter) { if ((*iter) && (*iter)->compare(_delegate)) { delete _delegate; return *this; } } mListDelegates.push_back(_delegate); return *this; } CMultiDelegate& operator-=(IDelegate* _delegate) { for (ListDelegateIterator iter=mListDelegates.begin(); iter!=mListDelegates.end(); ++iter) { if ((*iter) && (*iter)->compare(_delegate)) { if ((*iter) != _delegate) delete (*iter); (*iter) = 0; break; } } delete _delegate; return *this; } void operator()( ) { ListDelegateIterator iter = mListDelegates.begin(); while (iter != mListDelegates.end()) { if (0 == (*iter)) { iter = mListDelegates.erase(iter); } else { (*iter)->invoke(); ++iter; } } } private: CMultiDelegate (const CMultiDelegate& _event); CMultiDelegate& operator=(const CMultiDelegate& _event); private: ListDelegate mListDelegates; }; 其實最后這個類很像是一個指針容器,然后各個成員方法也只是對這個容器里面的對象進行管理。而主要的三個方法: 重載了 += 表示向這個委托注冊一個函數指針,這個方法會自動判重,如果重復了就不會向里面添加。 重載了 -= 表示向這個委托注銷一個函數指針,如果這個函數指針不存在就什么也不執行。 重載了 () 表示當作函數調用啟動這個委托,內部就是將所有函數指針指向的函數都運行一遍。 到這里,基本上無返回值、無參數的委托就封裝好了。我們先來測試一下: void Say() { printf("你好\n"); } class A { public : void Say(){ printf("你不好\n"); } }; int _tmain(int argc, _TCHAR* argv[]) { CMultiDelegate onclick; onclick += newDelegate(Say); onclick += newDelegate(&A(),&A::Say); //注意這里不能傳入 new A(), 因為會內存泄漏。 onclick(); 如果以上代碼能夠成功運行,那么說明你的第一個版本的委托已經封裝完畢,但是如何實現任意返回值、任意參數類型、任意參數個數的函數指針的委托呢? 我在網上查閱過許多代碼,發現大多數都是使用的宏替換加上多次引用頭文件使得每次編譯不同參數個數版本的委托,但是這個方法我感覺巧妙但卻雞肋。后來我嘗試着使用C11的新特性:可變模板參數實現了這個需求。能夠對用戶定義的不同委托去自動生成對應的函數指針類型的委托類。 具體的代碼詳見 C++實現委托機制(二)