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++實現委託機制(二)