最近,我一直在研究新的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事件外,KeyPress类还希望能够发送MouseMotion事件。

我考虑过使用多重继承。使Input继承EventBroadcaster<MouseMotion>EventBroadcaster<KeyPress>。这会导致编译器错误,提示功能不明确。以下答案Multiple Inheritance Template Class中提供的解决方案确实适用于 protected signal函数,但不适用于在connect类外部调用的公共(public)Input函数。

除了多重继承,我想知道可变参数模板是否可以帮助解决我的问题。我看过(部分)模板专门化和解压缩可变参数模板。但是一直无法提供(优雅的)解决方案。

支持多种事件类型的最佳方法是什么?

最佳答案

我将EventBroadcaster<T>设置为实现细节,而公开EventBroadcasters<Ts...>
EventBroadcasters<Ts...>拥有EventBroadcaster<Ts>...具有模板方法connect<U>signal<U>,并将它们转发给EventBroadcaster<U>。如果U不在Ts...中,则这将无法编译。

因此,现在您将signal(mouseMotion)解析为signal<MouseMotion>(mouseMotion),它可以连接到正确的广播者。

当您使用connect收听时,类似地,只要您使用正确的std::function类型,便可以正常工作。如果没有,您可以只传递您正在听的Event的类型。

有多种方法可以使其变得更加神奇,并实现所需的完整的,适当的过载解析,但这确实很棘手。即,您对connect进行检测以执行SFINAE并手动将传入的lambda(通过检查其operator())分派(dispatch)到正确的std::function覆盖。但是,仅能够说connect<MouseMotion>([&](MouseMotion m){...})应该是一个改进。

(SFINAE技术包括检查可以从传入的std::function中构造出列表中的哪种U类型,以及使用该类型是否唯一。此外,请检查U是否为std::function(或cv的cv变体)并不是很困难,但是看起来很凌乱,以我的经验,大多数人不喜欢类型转换,而是只指定<MouseMotion>。)

更新:

以下代码块提供了此答案的实现。

#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;
}

关于c++ - 使用模板进行事件处理,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15028609/

10-11 22:47