通用 C++ 回调映射,有没有更好的方法?
Generic C++ callback map, Is there any better way?
我正在尝试创建一个通用消息来处理我的代码。每条消息都由一个整数 id 标识。由于所有消息处理程序都有类似的减速,并且我喜欢快速处理每条消息,因此我使用 std::map
连接并为特定消息 ID 找到相应的消息处理程序。然后我调用这个处理程序并将消息传递给它。有几个是这样做的,这里是一个例子:
const std::map<int, void(*)(void*)> g_handlers = {
{1, h1},
{2, h2}
};
...
// message
int message_id = 2;
int data = 3;
// handle message
g_handlers[message_id](&data);
但是这种方法有几个大的限制:
- 由于有不同的消息,我们需要通过将它们作为
void*
参数传递来概括它们。这样,每个消息处理程序语法都将是 void (*)(void*)
,然后我们就可以将其用作 map 的值。
- 此消息没有类型检查。如果有人错误地为消息 id 2 添加了消息 id 1 的消息处理程序,我们可能无法快速找到此错误。
我想尝试一些新的东西,所以我试图找到解决这些问题的方法,我终于找到了一个工作代码。这是代码:
class handler_base {
public:
template <typename U>
void operator()(U* arg) {
run(arg, typeid(U));
}
private:
virtual void run(void* arg, const std::type_info& info) {}
};
template<typename T>
class handler : public handler_base {
public:
using type = T;
handler(void (*f)(T*)) :func(f) {
}
private:
void run(void* arg, const std::type_info& info) {
assert(info.hash_code() == typeid(T).hash_code());
func(static_cast<T*>(arg));
}
void (*func)(T*);
};
int main()
{
// 2 different types of handlers
handler h1(+[](double* v){ std::cout << "double called " << *v << "\n"; });
handler h2(+[](int* v){ std::cout << "int called " << *v << "\n"; });
const std::map<int, handler_base&> myhandler = {
{1, h1},
{2, h2}
};
double d = 1.5;
int i = 3;
myhandler.at(1)(&d);
//myhandler.at(1)(&i); // Error: failed assert due to type check
//myhandler.at(2)(&d); // Error: failed assert due to type check
myhandler.at(2)(&i);
}
下面是我的问题:
- 当地图为
const
时,使用 &
作为地图值是否有效?我知道当地图本身不是 const
但我想知道在这种情况下它是否正确。
- 有没有更简单的方法来做到这一点?使用具有类型检查的同一容器提供不同的回调消息处理程序语法?
- 您总体上如何看待这个想法?为类型检查和异构回调添加这种复杂性是个好主意吗?我个人总是遵循“简单是最好的”这一规则,我通常 select 第一种方法(使用通用的
void(*)(void*)
进行回调),但我喜欢知道你怎么看。
我认为你可以完全跳过基础class。您只需将函数指针直接存储为往返转换的函数指针。我还让它接受了很多参数:
#include <unordered_map>
#include <iostream>
#include <cassert>
struct Handler
{
template <typename T>
Handler(T fn)
: f((void(*)())(fn))
, info(typeid(T))
{
}
template <typename... Args>
void operator()(Args&&... args)
{
using Fn = void(Args...);
assert(info.hash_code() == typeid(Fn*).hash_code());
return ((Fn*)(f))(std::forward<Args>(args)...);
}
void (*f)();
const std::type_info& info;
};
int main()
{
std::unordered_map<int, Handler> cbmap;
cbmap.emplace(1, +[](int a, double b){std::cout << "1" << a << " " << b << "\n";});
cbmap.emplace(2, +[](double a){std::cout << "2" << a << "\n";});
cbmap.emplace(3, +[](double& a){std::cout << "3 " << a << "\n";});
double x = 42.0;
cbmap.at(1)(42,4.2);
cbmap.at(2)(4.2);
cbmap.at(3)(x);
}
我正在尝试创建一个通用消息来处理我的代码。每条消息都由一个整数 id 标识。由于所有消息处理程序都有类似的减速,并且我喜欢快速处理每条消息,因此我使用 std::map
连接并为特定消息 ID 找到相应的消息处理程序。然后我调用这个处理程序并将消息传递给它。有几个是这样做的,这里是一个例子:
const std::map<int, void(*)(void*)> g_handlers = {
{1, h1},
{2, h2}
};
...
// message
int message_id = 2;
int data = 3;
// handle message
g_handlers[message_id](&data);
但是这种方法有几个大的限制:
- 由于有不同的消息,我们需要通过将它们作为
void*
参数传递来概括它们。这样,每个消息处理程序语法都将是void (*)(void*)
,然后我们就可以将其用作 map 的值。 - 此消息没有类型检查。如果有人错误地为消息 id 2 添加了消息 id 1 的消息处理程序,我们可能无法快速找到此错误。
我想尝试一些新的东西,所以我试图找到解决这些问题的方法,我终于找到了一个工作代码。这是代码:
class handler_base {
public:
template <typename U>
void operator()(U* arg) {
run(arg, typeid(U));
}
private:
virtual void run(void* arg, const std::type_info& info) {}
};
template<typename T>
class handler : public handler_base {
public:
using type = T;
handler(void (*f)(T*)) :func(f) {
}
private:
void run(void* arg, const std::type_info& info) {
assert(info.hash_code() == typeid(T).hash_code());
func(static_cast<T*>(arg));
}
void (*func)(T*);
};
int main()
{
// 2 different types of handlers
handler h1(+[](double* v){ std::cout << "double called " << *v << "\n"; });
handler h2(+[](int* v){ std::cout << "int called " << *v << "\n"; });
const std::map<int, handler_base&> myhandler = {
{1, h1},
{2, h2}
};
double d = 1.5;
int i = 3;
myhandler.at(1)(&d);
//myhandler.at(1)(&i); // Error: failed assert due to type check
//myhandler.at(2)(&d); // Error: failed assert due to type check
myhandler.at(2)(&i);
}
下面是我的问题:
- 当地图为
const
时,使用&
作为地图值是否有效?我知道当地图本身不是const
但我想知道在这种情况下它是否正确。 - 有没有更简单的方法来做到这一点?使用具有类型检查的同一容器提供不同的回调消息处理程序语法?
- 您总体上如何看待这个想法?为类型检查和异构回调添加这种复杂性是个好主意吗?我个人总是遵循“简单是最好的”这一规则,我通常 select 第一种方法(使用通用的
void(*)(void*)
进行回调),但我喜欢知道你怎么看。
我认为你可以完全跳过基础class。您只需将函数指针直接存储为往返转换的函数指针。我还让它接受了很多参数:
#include <unordered_map>
#include <iostream>
#include <cassert>
struct Handler
{
template <typename T>
Handler(T fn)
: f((void(*)())(fn))
, info(typeid(T))
{
}
template <typename... Args>
void operator()(Args&&... args)
{
using Fn = void(Args...);
assert(info.hash_code() == typeid(Fn*).hash_code());
return ((Fn*)(f))(std::forward<Args>(args)...);
}
void (*f)();
const std::type_info& info;
};
int main()
{
std::unordered_map<int, Handler> cbmap;
cbmap.emplace(1, +[](int a, double b){std::cout << "1" << a << " " << b << "\n";});
cbmap.emplace(2, +[](double a){std::cout << "2" << a << "\n";});
cbmap.emplace(3, +[](double& a){std::cout << "3 " << a << "\n";});
double x = 42.0;
cbmap.at(1)(42,4.2);
cbmap.at(2)(4.2);
cbmap.at(3)(x);
}