最近,我一直在研究新的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/