gpt4 book ai didi

c++ - 是否可以在 C++ 中执行此 lambda 事件管理器?

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:27:17 27 4
gpt4 key购买 nike

我想编写一个支持传递任意数量参数的事件管理器。为了向您展示表格,这里有一个例子。请注意,一个目标是不需要为每个事件都定义一个类。相反,事件由字符串名称表示。首先,让我们为同一个事件注册四个监听器。它们接受的参数数量不同。

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

现在用一些参数触发事件。 events.fire("key", {42, "John"}); 这应该调用匹配部分或所有参数的已注册 lambda。例如,此调用应为我们注册的五个监听器产生以下结果。

  1. 打印“按下了一个键。”
  2. 打印“使用代码 42 按下的键。”
  3. 打印“约翰用代码 42 按下了键。”
  4. 抛出异常,因为监听器与签名不匹配。
  5. 抛出异常,因为监听器与签名不匹配。

是否可以在 C++ 中实现此行为?如果是这样,我怎样才能将不同的回调存储在一个集合中,同时仍然能够将它们转换回去以使用不同数量的参数进行调用?我认为这项任务并不容易,所以每个提示都有帮助。

最佳答案

我同意 Luc 的观点,即类型安全的方法可能更合适,但以下解决方案或多或少可以满足您的需求,但有一些限制:

  1. 参数类型必须是可复制的;
  2. 参数总是被复制,从不移动;
  3. 当且仅当 fire() 的前 N ​​个参数的类型与处理程序的参数类型完全匹配时,才会调用具有 N 个参数的处理程序,没有正在执行隐式转换(例如,从字符串文字到 std::string);
  4. 处理程序不能是具有多个重载 operator () 的仿函数。

这就是我的解决方案最终允许您编写的内容:

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

第一次调用 fire() 将产生以下输出:

Got a 42
Got a 42 and a hi
Bad arity!
Bad argument!
Bad arity!

虽然第二次调用将产生以下输出:

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!

这是一个live example .

实现基于boost::any。它的核心是 dispatcher 仿函数。它的调用运算符采用类型删除参数的 vector ,并将它们分派(dispatch)给构造它的可调用对象(您的处理程序)。如果参数类型不匹配,或者如果处理程序接受的参数多于提供的参数,它只会向标准输出打印一个错误,但如果你愿意,你可以让它抛出或做任何你喜欢的事情:

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

然后有几个实用程序用于从处理程序仿函数创建调度程序(有一个类似的实用程序用于从函数指针创建调度程序):

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

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

很明显,如果您的处理程序是一个具有多个重载调用运算符的仿函数,那么这整个事情将无法正常工作,但希望这个限制对您来说不会太严重。

最后,event_dispatcher 类允许您通过调用 listen() 将类型删除的处理程序存储在 multimap 中,并在您调用 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;
};

再一次,整个代码可用here .

关于c++ - 是否可以在 C++ 中执行此 lambda 事件管理器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25714390/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com