通用 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);

但是这种方法有几个大的限制:

  1. 由于有不同的消息,我们需要通过将它们作为 void* 参数传递来概括它们。这样,每个消息处理程序语法都将是 void (*)(void*),然后我们就可以将其用作 map 的值。
  2. 此消息没有类型检查。如果有人错误地为消息 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);  
}

下面是我的问题:

  1. 当地图为 const 时,使用 & 作为地图值是否有效?我知道当地图本身不是 const 但我想知道在这种情况下它是否正确。
  2. 有没有更简单的方法来做到这一点?使用具有类型检查的同一容器提供不同的回调消息处理程序语法?
  3. 您总体上如何看待这个想法?为类型检查和异构回调添加这种复杂性是个好主意吗?我个人总是遵循“简单是最好的”这一规则,我通常 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);
}