2013-02-22 81 views
1

最近我一直在使用新的C++ 11標準,並決定創建一個基本的事件處理系統。下面的代碼提供了我當前實現的一個小例子。使用模板進行事件處理

#include <functional> 
#include <vector> 
#include <iostream> 

template <typename Event> 
class EventBroadcaster 
{ 
public: 
    typedef std::function<void(const Event&)> Connection; 

    void connect(Connection&& connection) 
    { 
     connections.push_back(std::move(connection)); 
    } 

    void signal(const Event& event) 
    { 
     for (const auto& connection : connections) 
     { 
      connection(event); 
     } 
    } 
private: 
    std::vector<Connection> connections; 
}; 

struct MouseMotion 
{ 
    int x = 0; 
    int y = 0; 
}; 

class Input : public EventBroadcaster<MouseMotion> 
{ 
public: 
    void process() 
    { 
     MouseMotion mouseMotion; 
     mouseMotion.x = 10; 
     mouseMotion.y = 20; 
     signal(mouseMotion); 
    } 
}; 

int main() 
{ 
    int x = 0; 
    int y = 0; 

    Input input; 

    input.connect([&](const MouseMotion& e){ 
     x += e.x; 
     y += e.y; 
    }); 

    input.process(); 

    std::cout << x << "," << y << std::endl; // Output: 10,20 

    return 0; 
} 

上述解決方案確實工作相當不錯,如果Input類將只播出單一事件。然而,可能存在的情況是Input類別除了僅僅MouseMotion事件之外還希望能夠發送KeyPress事件。

我想過使用多重繼承。使Input繼承EventBroadcaster<MouseMotion>EventBroadcaster<KeyPress>。這會導致編譯器錯誤警告模糊函數。在下面的答案Multiple Inheritance Template Class中提供的解決方案確實可以用於受保護的signal函數,但不適用於公開的connect函數,該函數在Input類以外調用。

除了多繼承,我想知道如果variadic模板可以幫助我解決我的問題。我查看了(部分)模板專業化和解包可變參數模板。但一直無法與(優雅的)解決方案。

什麼是支持多種事件類型的最佳方式?

+0

你連接的解決方案怎麼不能用於'connect()'? – 2013-02-22 16:23:59

+0

將下列內容添加到Input類不起作用:'使用EventBroadcaster :: signal; 使用EventBroadcaster :: signal; 使用EventBroadcaster :: connect; 使用EventBroadcaster :: connect;'然而,我只是刪除'使用EventBroadcaster :: connect;'行後的MouseMotion事件。但仍然存在不能連接KeyPress事件的問題。 – daniel 2013-02-22 16:26:00

+0

不是你的問題的答案,而是'connections.push_back(std :: move(connection));'會更有效率,調用函數可以只是'連接(事件)'沒有額外的括號 – 2013-02-25 13:57:09

回答

2

我想讓EventBroadcaster<T>成爲一個實現細節,而不是公開EventBroadcasters<Ts...>

EventBroadcasters<Ts...>擁有EventBroadcaster<Ts>...有模板方法connect<U>signal<U>,以及這些轉發給EventBroadcaster<U>。如果U不在Ts...中,則應該編譯失敗。

所以現在你signal(mouseMotion),它解析爲signal<MouseMotion>(mouseMotion),它連接到正確的廣播公司。

當你聽,同樣只要你使用正確的std::function事情工作。如果不是的話,你可以傳遞你正在聽的Event的類型。

有辦法讓它更神奇,並且啓用你想要的全重合適重載分辨率,但這會變得非常棘手。也就是說,你用儀器connect來做SFINAE並手工調用傳入的lambda(通過檢查它的operator())到正確的std::function覆蓋。但是,只要能夠說connect<MouseMotion>([&](MouseMotion m){...})應該是一個改進。

(SFINAE技術將包括其std::function型出列表可以從您在U過去了,如果有一個獨特的,例如,使用一個。此外,檢查是否U這樣的std::function(或建造檢驗的。這樣的簡歷變體)它不是可笑的努力,但它是非常凌亂看,在我的經驗,多數人不喜歡的類型,噴涌,寧願只指定<MouseMotion>

UPDATE:

以下代碼塊提供了此答案的實現。

#include <functional> 
#include <vector> 
#include <iostream> 

namespace detail 
{ 
    template <typename Event> 
    class EventBroadcaster 
    { 
    public: 
     typedef std::function<void(const Event&)> Connection; 

     void connect(Connection&& connection) 
     { 
      connections.push_back(std::move(connection)); 
     } 

     void signal(const Event& event) 
     { 
      for (const auto& connection : connections) 
      { 
       connection(event); 
      } 
     } 
    private: 
     std::vector<Connection> connections; 
    }; 

    template <typename T> struct traits 
     : public traits<decltype(&T::operator())> {}; 

    template <typename C, typename R, typename A> 
    struct traits<R(C::*)(const A&) const> 
    { 
     typedef A type; 
    }; 
} 

template <typename... Events> 
class EventBroadcaster 
    : detail::EventBroadcaster<Events>... 
{ 
public: 
    template <typename Connection> 
    void connect(Connection&& connection) 
    { 
     typedef typename detail::traits<Connection>::type Event; 
     detail::EventBroadcaster<Event>& impl = *this; 
     impl.connect(std::move(connection)); 
    } 

    template <typename Event> 
    void signal(const Event& event) 
    { 
     detail::EventBroadcaster<Event>& impl = *this; 
     impl.signal(event); 
    } 

    virtual void processEvents() = 0; 
}; 

struct MouseMotion 
{ 
    int x = 0; 
    int y = 0; 
}; 

struct KeyPress 
{ 
    char c; 
}; 

class Input 
    : public EventBroadcaster<MouseMotion, KeyPress> 
{ 
public: 
    void processEvents() 
    { 
     MouseMotion mouseMotion; 
     mouseMotion.x = 10; 
     mouseMotion.y = 20; 
     signal(mouseMotion); 

     KeyPress keyPress; 
     keyPress.c = 'a'; 
     signal(keyPress); 
    } 
}; 

int main() 
{ 
    int x = 0; 
    int y = 0; 
    char c = '~'; 

    Input input; 

    input.connect([&](const MouseMotion& e){ 
     x += e.x; 
     y += e.y; 
    }); 

    input.connect([&](const KeyPress& e){ 
     c = e.c; 
    }); 

    input.processEvents(); 

    std::cout << c << " " << x << "," << y << std::endl; // Output: a 10,20 

    return 0; 
} 
1

如果是正常的話,你可以在你的Input類添加using指令爲KeyPress解決這個問題並存儲在一個std::function<>對象的拉姆達而不是使用auto

class Input : 
    public EventBroadcaster<MouseMotion>, 
    public EventBroadcaster<KeyPress> 
{ 
public: 
    using EventBroadcaster<MouseMotion>::signal; 
    using EventBroadcaster<MouseMotion>::connect; 

    // ADD THESE! 
    using EventBroadcaster<KeyPress>::signal; 
    using EventBroadcaster<KeyPress>::connect; 

    Input() {} 
    virtual ~Input() {} 
    void process() 
    { 
     MouseMotion mouseMotion; 
     mouseMotion.x = 10; 
     mouseMotion.y = 20; 
     signal(mouseMotion); 
    } 
}; 

int main() 
{ 
    Input input; 

    int myX = 0; 
    int myY = 0; 

    // STORE THE LAMBDA IN A function<> OBJECT: 
    std::function<void(const MouseMotion&)> onMouseMotion = [&](const MouseMotion& e){ 
     myX += e.x; 
     myY += e.y; 
    }; 

    // THIS WILL NOT BE AMBIGUOUS: 
    input.connect(onMouseMotion); 

    input.process(); 
    std::cout << myX << "," << myY << std::endl; // Output: 10,20 

    return 0; 
} 

更新:

接下來是上述解決方案的改進,不需要將lambda處理程序包裝在std::function對象中。這是足以改變connect方法的定義如下,讓SFINAE照顧排除不合適的重載:

template<typename F> 
void connect(F connection, decltype(std::declval<F>()(std::declval<Event>()))* = nullptr) 
{ 
    connections.push_back(connection); 
} 

現在可以調用connect()這樣:

input.connect([&](const MouseMotion& e){ 
    myX += e.x; 
    myY += e.y; 
    }); 

input.connect([&](const KeyPress& e){ 
    std::cout << e.c; 
    }); 
+0

在函數對象中包裝lambda確實可以消除模糊的編譯器錯誤。相反,編譯器給了我以下錯誤:'不匹配調用(std :: function )(const KeyPress&)'和'不匹配調用(std :: function )(const MouseMotion&)'。以某種方式混合KeyPress和MouseMotion。 – daniel 2013-02-22 16:47:10

+0

@Daniel:[this](http://liveworkspace.org/code/2CXJqe$0)適合我。你能顯示你正在嘗試的代碼嗎? – 2013-02-22 16:54:33

+0

發現問題。我傳遞連接使用右值引用連接,這似乎導致錯誤。在實際使用它們之前,我應該多讀些關於它們的內容。 :) – daniel 2013-02-22 17:13:46