dan*_*jar 6 c++ collections lambda templates initializer-list
我想编写一个支持传递任意数量参数的事件管理器.为了向您展示表单,这是一个示例.请注意,一个目标是不需要为每个事件定义类.相反,事件由字符串名称表示.首先,让4个侦听器注册到同一个事件.它们接受的参数数量不同.
Events events;
events.listen("key", [=] {
cout << "Pressed a key." << endl;
});
events.listen("key", [=](int code) {
cout << "Pressed key with code " << code << "." << endl;
});
events.listen("key", [=](int code, string user) {
cout << user << " pressed key with code " << code << "." << endl;
});
events.listen("key", [=](int code, string user, float duration) {
cout << user << " pressed key with code " << code << " for " << duration
<< " seconds." << endl;
});
events.listen("key", [=](string user) {
cout << user << " pressed a key." << endl;
});
Run Code Online (Sandbox Code Playgroud)
现在用一些参数激活事件.events.fire("key", {42, "John"});这应该调用匹配部分或全部参数的已注册lambda.例如,此调用应为我们注册的五个侦听器生成以下结果.
是否有可能在C++中实现此行为?如果是这样,我怎样才能将不同的回调存储在一个集合中,同时仍然能够将它们强制转换为使用不同数量的参数进行调用?我认为这个任务并不容易,所以每个提示都有帮助.
我同意 Luc 的观点,即类型安全的方法可能更合适,但以下解决方案或多或少可以满足您的需求,但有一些限制:
fire()匹配时,才会调用具有 N 个参数的处理程序,并且不执行隐式转换(例如,从字符串文字到);std::stringoperator ()。这就是我的解决方案最终允许您编写的内容:
void my_handler(int x, const char* c, double d)
{
std::cout << "Got a " << x << " and a " << c
<< " as well as a " << d << std::endl;
}
int main()
{
event_dispatcher events;
events.listen("key",
[] (int x)
{ std::cout << "Got a " << x << std::endl; });
events.listen("key",
[] (int x, std::string const& s)
{ std::cout << "Got a " << x << " and a " << s << std::endl; });
events.listen("key",
[] (int x, std::string const& s, double d)
{ std::cout << "Got a " << x << " and a " << s
<< " as well as a " << d << std::endl; });
events.listen("key",
[] (int x, double d)
{ std::cout << "Got a " << x << " and a " << d << std::endl; });
events.listen("key", my_handler);
events.fire("key", 42, std::string{"hi"});
events.fire("key", 42, std::string{"hi"}, 3.14);
}
Run Code Online (Sandbox Code Playgroud)
第一次调用fire()将产生以下输出:
Got a 42
Got a 42 and a hi
Bad arity!
Bad argument!
Bad arity!
Run Code Online (Sandbox Code Playgroud)
第二次调用将产生以下输出:
Got a 42
Got a 42 and a hi
Got a 42 and a hi as well as a 3.14
Bad argument!
Bad argument!
Run Code Online (Sandbox Code Playgroud)
这是一个活生生的例子。
实现基于boost::any. 它的核心是dispatcher函子。它的调用运算符采用类型擦除参数的向量,并将它们分派给构造它的可调用对象(您的处理程序)。如果参数类型不匹配,或者处理程序接受的参数多于提供的参数,它只会将错误打印到标准输出,但如果您愿意或执行任何您喜欢的操作,您可以让它抛出:
template<typename... Args>
struct dispatcher
{
template<typename F> dispatcher(F f) : _f(std::move(f)) { }
void operator () (std::vector<boost::any> const& v)
{
if (v.size() < sizeof...(Args))
{
std::cout << "Bad arity!" << std::endl; // Throw if you prefer
return;
}
do_call(v, std::make_integer_sequence<int, sizeof...(Args)>());
}
private:
template<int... Is>
void do_call(std::vector<boost::any> const& v, std::integer_sequence<int, Is...>)
{
try
{
return _f((get_ith<Args>(v, Is))...);
}
catch (boost::bad_any_cast const&)
{
std::cout << "Bad argument!" << std::endl; // Throw if you prefer
}
}
template<typename T> T get_ith(std::vector<boost::any> const& v, int i)
{
return boost::any_cast<T>(v[i]);
}
private:
std::function<void(Args...)> _f;
};
Run Code Online (Sandbox Code Playgroud)
然后有几个实用程序用于从处理程序函子创建调度程序(有一个类似的实用程序用于从函数指针创建调度程序):
template<typename T>
struct dispatcher_maker;
template<typename... Args>
struct dispatcher_maker<std::tuple<Args...>>
{
template<typename F>
dispatcher_type make(F&& f)
{
return dispatcher<Args...>{std::forward<F>(f)};
}
};
template<typename F>
std::function<void(std::vector<boost::any> const&)> make_dispatcher(F&& f)
{
using f_type = decltype(&F::operator());
using args_type = typename function_traits<f_type>::args_type;
return dispatcher_maker<args_type>{}.make(std::forward<F>(f));
}
Run Code Online (Sandbox Code Playgroud)
帮助function_traits器是一个简单的特征,用于确定处理程序的类型,因此我们可以将它们作为模板参数传递给dispatcher:
template<typename T>
struct function_traits;
template<typename R, typename C, typename... Args>
struct function_traits<R(C::*)(Args...)>
{
using args_type = std::tuple<Args...>;
};
template<typename R, typename C, typename... Args>
struct function_traits<R(C::*)(Args...) const>
{
using args_type = std::tuple<Args...>;
};
Run Code Online (Sandbox Code Playgroud)
显然,如果您的处理程序是一个具有多个重载调用运算符的函子,那么整个事情将无法工作,但希望这个限制对您来说不会太严重。
最后,该类event_dispatcher允许您通过调用将类型擦除的处理程序存储在多重映射中,并在您使用适当的键和适当的参数listen()调用时调用它们(您的对象将是此类的实例):fire()events
struct event_dispatcher
{
public:
template<typename F>
void listen(std::string const& event, F&& f)
{
_callbacks.emplace(event, make_dispatcher(std::forward<F>(f)));
}
template<typename... Args>
void fire(std::string const& event, Args const&... args)
{
auto rng = _callbacks.equal_range(event);
for (auto it = rng.first; it != rng.second; ++it)
{
call(it->second, args...);
}
}
private:
template<typename F, typename... Args>
void call(F const& f, Args const&... args)
{
std::vector<boost::any> v{args...};
f(v);
}
private:
std::multimap<std::string, dispatcher_type> _callbacks;
};
Run Code Online (Sandbox Code Playgroud)
再一次,完整的代码可以在这里找到。