基于命令ID分发不同参数类型的C++函数调度器实现疑问
基于命令ID的类型安全函数调度实现方案
问题背景
我需要实现一个基于命令ID的函数调度逻辑:
- 输入是命令ID(int类型)和字节缓冲区,每个ID对应一个编译期已知的自定义数据类型,缓冲区的字节数据和该类型完全匹配
- 每个回调函数需要用到程序启动时就初始化好的内部数据指针(比如
GiantFoo的成员变量)
当前尝试用std::function<int(BaseArgs*)>作为统一签名存在映射里,但在main函数中仅知道ID的情况下,不知道怎么把通用的void*数据转换成对应的DerivedArgsN类型并传给回调,不确定是不是要额外维护一个ID到DerivedArgs实例的映射,或者有没有更简单的方法。
优化实现方案
不需要额外维护DerivedArgs的映射,直接把类型转换、内部数据关联、回调调用打包成统一的调度单元,用模板或者lambda消除重复代码,同时避免dynamic_cast的运行时开销,保证类型安全。
方案一:模板包装函数(高性能)
这种方式用模板生成类型安全的调度包装函数,直接用函数指针存储,性能最优:
#include <functional> #include <unordered_map> #include <cstdint> #include <cstring> #include <cstdio> // 内部数据结构 struct MinorFoo0 { int a; }; struct MinorFoo1 { int a; }; struct GiantFoo { MinorFoo0 foo0; MinorFoo1 foo1; }; GiantFoo giant_foo; // 命令对应的数据类型 struct DataFoo0 { int x; }; struct DataFoo1 { int x; }; // 业务回调的统一签名 using BusinessCallback = int(void*, void*); // 调度器函数签名:接收内部数据指针、命令数据指针 using DispatchFn = int(void*, void*); // 模板包装函数:编译期完成类型转换,转发到业务回调 template<typename InternalT, typename DataT, BusinessCallback Callback> int dispatch_wrap(void* internal, void* data) { return Callback(static_cast<InternalT*>(internal), static_cast<DataT*>(data)); } // 实际业务逻辑函数 int foo0_handler(MinorFoo0* internal, DataFoo0* data) { internal->a = data->x; // 直接操作强类型指针 return 0; } int foo1_handler(MinorFoo1* internal, DataFoo1* data) { internal->a = data->x * 2; return 1; } enum CmdID { FOO_0 = 0, FOO_1 = 1 }; // 调度表:每个ID对应调度函数+内部数据指针 struct DispatchEntry { DispatchFn* fn; void* internal_data; }; std::unordered_map<int, DispatchEntry> dispatch_table = { {FOO_0, {dispatch_wrap<MinorFoo0, DataFoo0, foo0_handler>, &giant_foo.foo0}}, {FOO_1, {dispatch_wrap<MinorFoo1, DataFoo1, foo1_handler>, &giant_foo.foo1}} }; int main() { uint8_t data_buf[100] = {}; int cmd_id = FOO_0; void* raw_data = data_buf; // 模拟填充命令数据 DataFoo0 test_data{42}; std::memcpy(data_buf, &test_data, sizeof(test_data)); // 执行调度 auto iter = dispatch_table.find(cmd_id); if (iter != dispatch_table.end()) { auto& entry = iter->second; entry.fn(entry.internal_data, raw_data); } // 验证结果 printf("giant_foo.foo0.a = %d\n", giant_foo.foo0.a); // 输出42 return 0; }
方案二:Lambda封装(简洁易读)
如果命令数量不多,用lambda直接封装更简洁,不需要额外写模板函数:
#include <functional> #include <unordered_map> #include <cstdint> #include <cstring> #include <cstdio> // 内部数据和命令数据结构 struct MinorFoo0 { int a; }; struct MinorFoo1 { int a; }; struct GiantFoo { MinorFoo0 foo0; MinorFoo1 foo1; }; GiantFoo giant_foo; struct DataFoo0 { int x; }; struct DataFoo1 { int x; }; // 业务逻辑函数 int foo0_handler(MinorFoo0* internal, DataFoo0* data) { internal->a = data->x; return 0; } int foo1_handler(MinorFoo1* internal, DataFoo1* data) { internal->a = data->x * 2; return 1; } enum CmdID { FOO_0 = 0, FOO_1 = 1 }; // 调度表直接存储lambda,内部完成类型转换 std::unordered_map<int, std::function<int(void*)>> dispatch_table = { {FOO_0, [](void* data) { return foo0_handler(&giant_foo.foo0, static_cast<DataFoo0*>(data)); }}, {FOO_1, [](void* data) { return foo1_handler(&giant_foo.foo1, static_cast<DataFoo1*>(data)); }} }; int main() { uint8_t data_buf[100] = {}; int cmd_id = FOO_1; void* raw_data = data_buf; DataFoo1 test_data{10}; std::memcpy(data_buf, &test_data, sizeof(test_data)); auto iter = dispatch_table.find(cmd_id); if (iter != dispatch_table.end()) { iter->second(raw_data); } printf("giant_foo.foo1.a = %d\n", giant_foo.foo1.a); // 输出20 return 0; }
核心优势
- 类型安全:所有类型转换都在编译期完成,不存在运行时类型转换失败的风险
- 无冗余开销:不需要基类、虚函数、
dynamic_cast,也不需要临时构造DerivedArgs对象 - 扩展性好:新增命令只需要3步:定义数据类型、写业务函数、往调度表加一行
- 代码清晰:业务函数直接接收强类型指针,不用处理通用指针的转换逻辑
内容的提问来源于stack exchange,提问作者rookie
相关产品推荐
相关产品推荐

