實現基於C++的動態事件機制(轉)

事件支持已經是Delphi,Java,C#這樣的後起語言的語法之一,但是在C++中並沒有顯示的支持。不同的編譯器採用各自的方法來提供對事件的支持,例如:Borland C++ Builder通過擴展語法來提供事件支持,以實現VCL;MFC的事件是在設計時由嚮導生成的基於表格驅動的靜態事件,不提供運行時支持。這兩種實現都只能在特定場景下工作,而且難於移植。因此,我們需要一個完全基於C++語法的事件機制,不能使用編譯器對C++語法的擴展關鍵字,並且,該機制應該是動態的,即能夠在運行時改變事件屬性,這個屬性通常是指與事件相關聯的事件處理過程的方法或函數的地址,能夠動態個性屬性的事件具有更大的靈活性和易於使用。
 
要實現這一目的,可以參考一下Delphi,Java,C#,包括BCB的VCL,在這些語言中(或開發工具)中事件的實現或表示機制,還可以參考一下COM的連接點技術。很容易就可以發現,它們有一個共同點:所有對象都源自一個公共基類,尤其是BCB,如果對象不是繼承自TObject的話,就不能使用事件。參考COM的連接點實現,也可以發現一些有意思的東西,特別是它調用連接點事件的手段。同時,MFC建立事件表格的方法,也值得借鑑。
 
使用公共基類,派生對象可以向上映射,這樣就可以對事件的表現形式進行統一定義。使用公共基類還有另外一個重要意義:當我們在C++中獲取對象的方法地址時,實際上取得是對象方法在虛表中偏移,而源自相同基類的對象的方法的偏移是各不相同的,即它們的虛表具有統一的佈局,而源自不同基類的對象的方法則可能相同。
 
在進行下一步討論之前,我們先看看在C++中如何通過指針來調用對象的方法。
 
在C++中,可以通過以下形式來獲得一個對象的方法地址:
&對象::方法
   
    這樣得到的地址其實是方法在對象的虛表內的16位偏移,因此,還需要一個對象的實例指針才能正確的調用方法。可以使用以下語句來調用實例中的方法:
(對象的實例指針->*方法地址)(參數)
 
通常,我們需要將方法地址映射爲一個通用的表現形式:
static_cast<通用的方法表現形式>(&對象::方法)
 
舉例來說,對於兩個類A,B的方法A::func1,B::func2,當它們源自相同基類時,則在虛表中具有不同偏移,一個可能爲1,另一個可能爲5,但絕不會相同,假設取得的地址爲:addr1,addr2。
那麼就有:
    addr1 = static_cast<表現定義>(&A::func1);
    addr2 = static_cast<表現定義>(&B::func2);
 
無論何時,如果按以下形式調用兩個方法,則是正確的:
    (對象X指針->*addr1)();// 這會調用偏移1處的方法
    (對象Y指針->*addr2)();// 這會調用偏移5處的方法
 
    但是,當它們源自不同基類時,則無法進行統一形式的static_cast映射,並且,方法有可能具有相同的偏移,這樣兩個調用都會嘗試同一個方法,這會導致錯誤。
   
通過公共基類,可以將事件的處理方法屬性關聯到任意符合定義的方法上,這樣就可以在運行時修改事件屬性,實現動態事件機制。當然,在設置事件的關聯處理方法-通常叫事件句柄(handler)-的同時,我們還需要保存一個具有該方法的對象的實例指針。這樣當事件發生時,我們就能夠在正確的對象實例上調用正確的方法,完成正確的事件處理。
 
下面是基於上面方法的的實現代碼,使用了BCB的命名習慣和doxgen的註釋風格:
 
/**
* 基類
*/
class TObject {};
 
/**
* 事件參數類
*/
template < class T >
class TEventArg : public TObject
{
    T value;
public:
    TEventArg():value() { }
 
    TEventArg(T x) { value = x; }
 
    TEventArg(const TEventArg& ref) { value = ref; }
 
    TEventArg<T>& operator = (T x)
    {
        value = x;
        return *this;
    }
 
    TEventArg<T>& operator = (TEventArg<T> x)
    {
        value = x;
 
        return *this;
    }
 
    operator T()
    {
        return value;
    }
};
 
 
/**
* 事件類
*/
template < class T>
class TEvent : public TObject
{
public:
    /**
    * 事件句柄類型定義
    */
    typedef void (TObject::*TEventHandler)(TEventArg<T>[]);
 
private:
    /**
    * 保存事件處理方法的句柄數組
 * 通常只要保存一個事件處理方法就可以
* 這裏是爲了演示一個事件上激發多個處理句柄的情況,使用了數組來
* 保存所有句柄
*/
    vector<pair<TObject*, TEventHandler> > _HandlerArray;
   
public:
    /**
    * 清除所有事件處理方法的句柄
    */
    void clear()
    {
        _HandlerArray.clear();
    }
 
    /**
    * 移除事件處理方法句柄
    */
    void remove(pair<TObject*, TEventHandler> handler)
    {
        // 通常事件只有一個或數個,所以爲了簡單起見和說明用途,使用了for循環
        for ( vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin();
                it != _handlerArray.end(); it++ )
        {
            if ( (*it).first == handler.first &&
                 *(int *).second == *(int *)handler.second )
            {
                it = _HandlerArray.erase(it);
            }
        }
    }
 
    /**
    * 句柄的賦值操作,清空已有的句柄,並用新句柄代替
    * @see add
    */
    TEvent<T>& operator = (pair<TObject*, TEventHandler> handler)
    {
        clear();
 
        _HandlerArray.push_back(handler);
 
        return *this;
    }
 
    /**
    * 添加事件處理方法句柄,在實際情況中,比operator +=操作簡單
    */
    void add(TObject* object, TEventHandler handler)
    {
        _HandlerArray.push_back(make_pair<TObject*, TEventHandler>(object, handler));
    }
   
    /**
    * 添加事件處理方法句柄
    */
    TEvent<T>& operator += (pair<TObject*, TEventHandler> handler)
    {
        _HandlerArray.push_back(handler);
       
        return *this;
    }
   
    /**
    * 刪除句柄
    */
    TEvent<T>& operator += (pair<TObject*, TEventHandler> handler)
    {
        remove(handler);
 
        return *this;
    }
 
    /**
    * 激發事件
    */
    void fire(TEventArg<T> args[])
    {
        /**
        * 循環激發所有事件
        */
        for(vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin();
            it != _HandlerArray.end(); it++)
        {
            if ( (*it).first != NULL && (*it).second != NULL )
            {
                // 這裏是示意如何調用事件句柄,形成激發事件的
                // 爲了示意,寫成三步
                TObject* object = (*it).first;
                TEventHandler handler = (*it).second;
 
                (object->*handler)(args);
 
                //((*it).first->*((*it).second))(args); // 簡短的形式一步就可以了
            } // if
        }// for
    }// method fire
};
 
/**
* 事件的激發源
*/
template < class T >
class TMyEventFirer : public TObject
{
public:
    /// 事件
    TEvent<T> MyEvent;
   
    /// 某個將會激發事件的方法
    void fireEvent(TEventArg<T> args[])
    {
        MyEvent.fire(args);
    }
};
 
/**
* 處理事件的對象之一
*/
class A : public TObject
{
public:
    A() { }
 
public:
    /**
    * 事件處理方法
    */
    void func(TEventArg<int> args[])
    {
        cout<<"A:func:"<<(int)args[0]<<endl;
    }
};
 
/**
* 處理事件的對象之二
*/
class B : public TObject
{
public:
    B() { }
 
public:
    /**
    * 事件處理方法
    */
    void func(TEventArg<int> args[])
    {
        cout<<"B:func:"<<(int)args[0]<<endl;
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    TMyEventFirer<int> firer; // 事件源
    A a; // 事件處理對象
    B b; // 事件處理對象
 
    //添加A對象的事件處理
    firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&a, static_cast<TEvent<int>::TEventHandler>(&A::func));
   
    //添加B對象的事件處理
    firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&b, static_cast<TEvent<int>::TEventHandler>(&B::func));
   
    // 再次添加A對象的事件處理
    // 由於沒有判別唯一性,所以A對象的事件處理會被激發次
    firer.MyEvent.add(&a, static_cast<TEvent<int>::TEventHandler>(&A::func));
   
    //準備參數
    TEventArg<int> args[1];
   
    // 初始化
    args[0] = 999;
   
    // 激發事件
    firer.fireEvent(args);
 
    return 0;
}
 
 這個粗陋的例子應該足夠演示如何實現動態事件機制了,剩下的就請自行充實完成。
 
轉載自http://blog.csdn.net/igame/article/details/1748056
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章