如何实现类型到方法调用的映射 解决静态容器初始化异常等问题
实现方案推荐
下面提供三种不同适用场景的优雅实现,均可以解决你提到的两个问题:
方案1:局部静态懒加载映射(通用度最高,改造成本最低)
把全局静态map移到函数内部作为局部静态变量,利用C++的局部静态变量首次调用时初始化的特性,把初始化时机推迟到main函数执行之后,异常可以正常捕获,原有逻辑几乎不需要修改:
#include <unordered_map> #include <functional> #include <iostream> #include <stdexcept> int DoTask1(int num) { std::cout << "DoTask1" << std::endl; return num; } int DoTask2(int num) { std::cout << "DoTask2" << std::endl; return num; } // 仅首次调用时初始化映射表 const std::unordered_map<int, std::function<int(int)>>& GetFuncMap() { static const std::unordered_map<int, std::function<int(int)>> httpSrvFunc = { {1, DoTask1}, {2, DoTask2} }; return httpSrvFunc; } int main() { try { // 调用方式仅比原来多一层GetFuncMap() GetFuncMap().at(1)(1); GetFuncMap().at(2)(1); } catch (const std::exception& e) { // 可正常捕获映射表初始化、key查找抛出的所有异常 std::cerr << "error: " << e.what() << std::endl; } return 0; }
优势:
- 支持任意可作为hash map key的类型(枚举、字符串等都兼容)
- 新增映射仅需要在初始化列表加一行,不需要修改其他逻辑
- 完全兼容原有业务逻辑,改造成本极低
方案2:编译期静态查找表(无初始化异常风险,性能更高)
如果你的映射关系是固定不变的,且key的数量不多,可以用std::array存储编译期确定的键值对,array的构造是noexcept的,完全没有静态初始化异常风险,也支持枚举等非连续值作为key:
#include <array> #include <functional> #include <stdexcept> #include <iostream> int DoTask1(int num) { std::cout << "DoTask1" << std::endl; return num; } int DoTask2(int num) { std::cout << "DoTask2" << std::endl; return num; } // 编译期构建查找表,初始化完全安全 constexpr std::array<std::pair<int, std::function<int(int)>>, 2> g_func_table = {{ {1, DoTask1}, {2, DoTask2} }}; int InvokeByKey(int key, int num) { for (const auto& item : g_func_table) { if (item.first == key) { return item.second(num); } } throw std::invalid_argument("invalid key"); } int main() { try { InvokeByKey(1, 1); InvokeByKey(2, 1); } catch (const std::exception& e) { std::cerr << "error: " << e.what() << std::endl; } return 0; }
优势:
- 无任何初始化异常风险,静态构造过程是
noexcept的 - key数量少于20个的场景下,遍历查找性能远高于hash map
- 支持任意可比较的key类型,不需要连续数值作为数组下标
方案3:编译期模板特化映射(零运行期开销)
如果你的key值在调用时是编译期已知的常量,可以用模板特化实现完全无运行期开销的映射:
#include <iostream> int DoTask1(int num) { std::cout << "DoTask1" << std::endl; return num; } int DoTask2(int num) { std::cout << "DoTask2" << std::endl; return num; } // 通用模板声明 template <int Key> int ExecTask(int num); // 按key特化实现 template <> int ExecTask<1>(int num) { return DoTask1(num); } template <> int ExecTask<2>(int num) { return DoTask2(num); } int main() { // 编译期确定调用函数,无任何运行期查找开销 ExecTask<1>(1); ExecTask<2>(1); return 0; }
优势:
- 零运行期查找开销,性能最优
- 完全没有初始化、查找相关的异常风险
方案选择建议
- 绝大多数普通业务场景选方案1即可,改造成本低,灵活度最高
- 对性能要求高、映射关系固定的场景选方案2
- key为编译期常量的场景选方案3
内容的提问来源于stack exchange,提问作者Hugh Men
相关产品推荐
相关产品推荐

