2011-02-02 51 views
4

我有一個基類的功能,其實現了以下:傳類型信息,以代替虛擬模板函數C++

struct Consumer 
{ 
    template <typename T> 
    void callback(T msg) { /*null implementation */ } 
}; 

我然後有一個類實現此:

struct Client : public Consumer 
{ 
    void callback(Msg1 msg); 
    void callback(Msg2 msg); 
    void callback(Msg3 msg); 
}; 

的問題是我有一個客戶端對象的容器被視爲消費者*,我想不出一種方法來讓這些消費者對象調用派生函數。我的預期功能是讓多個客戶端爲每個Msg類實現一個重載函數,這對他們意味着什麼,其餘的調用只需在基類中調用null實現

任何想法如何獲得派生類被調用?現在我需要在Consumer中實現每個重載函數,並將它們標記爲虛擬。

乾杯, 格雷姆

+1

他是什麼虛擬功能 – KitsuneYMG 2011-02-02 09:29:11

+0

虛擬功能不是我關心的;它爲每個可能的消息類都重載。 – Graeme 2011-02-02 09:34:27

+2

你可以讓msg多態 - 以及擺脫模板?你有多少個msg類? – Anycorn 2011-02-02 09:40:06

回答

3

如果你真的不想使用虛擬功能(這似乎是一個完美的使用情況下,爲他們真正的,但我不知道你的消息類),您可以使用CRTP

template <typename U> 
struct Consumer 
{ 
    template <typename T> 
    void callback(T msg) 
    { static_cast<U*>(this)->callback(msg); } 
}; 


struct Client : Consumer<Client> 
{ 
    void callback(Msg1 msg); 
    void callback(Msg2 msg); 
    void callback(Msg3 msg); 
}; 

的問題,當然,是你不能存儲在一個容器中的任何更Consumer對象。由於一切都是編譯時間,所以客戶端的實際類型必須與消費者對象一起存儲,供編譯器調用正確的回調函數。虛擬功能可以讓你等待直到運行這個...

是否有一個原因沒有Msg類多態和使用標準的虛擬功能(除「我必須重寫所有的代碼,我不能」)?

編輯如果你擔心的是消息類,爲什麼不使用類似的東西,假設消息類實現DoSomething成員函數:(這個技術被稱爲類型擦除

struct AnyMsg 
{ 
    template <typename Msg> 
    AnyMsg(Msg x) : impl(newImpl(x)) {} 

    void DoSomething() { impl->DoSomething(); } 

private: 
    struct Impl 
    { 
     virtual ~Impl() {} 
     virtual void DoSomething() = 0; 
    }; 

    // Probably better is std::unique_ptr if you have 
    // C++0x. Or `boost::scoped_ptr`, but you have to 
    // provide copy constructors yourself. 
    boost::shared_ptr<Impl> impl; 

    template <typename Msg> 
    Impl* newImpl(Msg m) 
    { 
     class C : public Impl 
     { 
      void DoSomething() { x.DoSomething(); } 
      Msg x; 

     public: 
      C(Msg x) : x(x) {} 
     }; 

     return new C(m); 
    } 
}; 

你可以自定義newImpl的行爲以得到你想要的東西(例如,如果消息類中沒有成員函數,某些消息類或其他任何東西沒有專門化,則默認操作)。這樣,您就可以實現Msg類,就像您使用模板解決方案所做的那樣,並且您擁有一個可以傳遞到客戶類中的虛擬函數的獨特外觀。

如果Message類將會有很大的不同,並且客戶端類可能會對它們做出不同的反應,並且您將有很多消息類,這會開始聞起來。或者也許你有一個醜陋可怕的候選人Visitor pattern

1

由於您不想使用虛擬方法,因此編譯器必須靜態地(即在編譯時)知道要調用哪個函數。如果您的容器中有不同的客戶端對象,現在編譯器可能會知道這一點。所以我認爲沒有使用虛擬方法(這是爲這種情況準確設計的)而沒有解決方案。

當然,你可以使用或者一些switch報表手動導出具體類型,但是這既不優雅,也不高效(你將不得不硬編碼的所有可能的客戶端類型...)

編輯

個人而言,我實現包含類型代碼中的一些基站消息類,並在客戶端類實現一個switch語句來處理不同的消息類型,如:

struct MsgBase { 
    int type; 
}; 

struct Consumer { 
    virtual void callback(MsgBase msg) { }; 
}; 

struct Client : public Consumer { 
    void callback(MsgBase msg) { 
     switch (msg.type) { 
     case MSGTYPE1: 
      callback((Msg1)msg); 
      break; 
     case MSGTYPE2: 
      callback((Msg2)msg); 
      break; 
     // ... 
     } 
    } 
    void callback(Msg1 msg) { /* ... */ } 
    void callback(Msg2 msg) { /* ... */ } 
}; 

您也可以使MsgBase多態(例如,虛析構函數),並使用typeid區分(更優雅,但略低於效率...)

struct Client : public Consumer { 
    void callback(MsgBase* msg) { 
     if (typeid(*msg) == typeof(Msg1)) 
      callback(static_cast<Msg1*>(msg)); 
     else if (typeid(*msg) == typeof(Msg2)) 
      callback(static_cast<Msg2*>(msg)); 
    } 
    // ... 
}; 
1

這始終是一個困難的局面,使完全可擴展的,與訪問者模式是通常的情況下。

您最終需要高達V * T的實現,其中V是「訪問者」的數量,T是正在訪問的類型的數量,並且可能最終不得不使用訪客和類工廠模式的混合。

這裏的訪問者將是您的消費者 類工廠將用於消息類型。

並且使其完全可擴展的最佳方法是爲消息/消費者對創建新的函數「對象」,並且雙重調度以確保正確的調用。

在你的情況下,你有不同的信息進來,然後你把它們給你的消費者誰可以處理它們?所以每條消息應該有一個可識別的「類型」,您的消費者應該在表中查找此類型以爲其創建處理程序。

您可以爲每個消費者類每種類型設置一個處理程序。