运行时基于枚举值选择类实例化的C++实现问题
问题描述
想要根据enum class的运行时值,构造对应子类的对象并返回基类的shared_ptr。类A和B拥有多个参数数量不同的构造函数,这些构造函数可能在运行时被调用。但当前的工厂方法实现会触发编译错误:
给出的代码实现:
enum class Type { A, B // 可扩展更多类型 }; struct Base{}; struct A : public Base { A(const int a) { std::cout << "A::A(int)"; } A(double d) { std::cout << "A::A(double)"; } A(int a, double d) { std::cout << "A::A(int,double)"; } }; struct B : public Base { B(int i, double d) { std::cout << "B::B(double)"; } B(int a, double d, double dd) { std::cout << "B::B(int,double, double)"; } }; template <typename... Args> std::shared_ptr<Base> make_Type(Type t, Args &&...args) { if (t == Type::A) return std::make_shared<A>(std::forward<Args>(args)...); else if (t == Type::B) return std::make_shared<B>(std::forward<Args>(args)...); else assert(false); }
调用make_Type(Type::A,1);时,GCC14会报错:
error: no matching function for call to 'B::B(int)'
原因是编译器会检查所有分支的代码合法性,即使运行时不会走到B的分支。已知可以用if constexpr实现静态类型选择,但需要运行时版本的实现(尽量不使用typeinfo),求可行的替代方案。
解决方案
方案1:为不同参数签名重载工厂函数
针对每种可能的参数组合,单独编写工厂函数的重载版本。每个重载只处理当前参数组合下合法的类型分支:
#include <memory> #include <iostream> #include <cassert> enum class Type { A, B }; struct Base{}; struct A : public Base { A(const int a) { std::cout << "A::A(int)\n"; } A(double d) { std::cout << "A::A(double)\n"; } A(int a, double d) { std::cout << "A::A(int,double)\n"; } }; struct B : public Base { B(int i, double d) { std::cout << "B::B(int,double)\n"; } B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; } }; // 单int参数的工厂重载 std::shared_ptr<Base> make_Type(Type t, int a) { if (t == Type::A) return std::make_shared<A>(a); else assert(false); // B不支持单int参数,直接触发断言 } // 单double参数的工厂重载 std::shared_ptr<Base> make_Type(Type t, double d) { if (t == Type::A) return std::make_shared<A>(d); else assert(false); } // int+double参数的工厂重载 std::shared_ptr<Base> make_Type(Type t, int a, double d) { if (t == Type::A) return std::make_shared<A>(a, d); else if (t == Type::B) return std::make_shared<B>(a, d); else assert(false); } // int+double+double参数的工厂重载 std::shared_ptr<Base> make_Type(Type t, int a, double d, double dd) { if (t == Type::B) return std::make_shared<B>(a, d, dd); else assert(false); }
优缺点:实现简单直观,编译期就能检查参数与构造函数的匹配性;但如果参数组合较多,需要编写大量重载函数,扩展性较差。
方案2:使用函数表注册构造逻辑
提前为每种类型和参数组合注册对应的构造函数,运行时通过类型标识和参数签名查找并调用构造逻辑:
#include <memory> #include <iostream> #include <cassert> #include <unordered_map> #include <tuple> #include <typeindex> #include <functional> enum class Type { A, B }; struct Base{}; struct A : public Base { A(const int a) { std::cout << "A::A(int)\n"; } A(double d) { std::cout << "A::A(double)\n"; } A(int a, double d) { std::cout << "A::A(int,double)\n"; } }; struct B : public Base { B(int i, double d) { std::cout << "B::B(int,double)\n"; } B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; } }; // 定义构造函数的函数类型 template <typename... Args> using Constructor = std::function<std::shared_ptr<Base>(Args&&...)>; // 全局函数表:键为(Type, 参数类型元组的type_index),值为构造函数指针 std::unordered_map< std::pair<Type, std::type_index>, void*, decltype([](const auto& k) { return std::hash<int>()(static_cast<int>(k.first)) ^ std::hash<std::type_index>()(k.second); }) > g_constructor_table; // 注册构造函数的模板工具 template <typename Derived, typename... Args> void register_constructor(Type t) { using ArgsTuple = std::tuple<std::decay_t<Args>...>; auto key = std::make_pair(t, std::type_index(typeid(ArgsTuple))); g_constructor_table[key] = reinterpret_cast<void*>( new Constructor<Args...>([](Args&&... args) { return std::make_shared<Derived>(std::forward<Args>(args)...); }) ); } // 工厂函数实现 template <typename... Args> std::shared_ptr<Base> make_Type(Type t, Args&&... args) { using ArgsTuple = std::tuple<std::decay_t<Args>...>; auto key = std::make_pair(t, std::type_index(typeid(ArgsTuple))); auto it = g_constructor_table.find(key); if (it == g_constructor_table.end()) { assert(false); return nullptr; } auto ctor = *reinterpret_cast<Constructor<Args...>*>(it->second); return ctor(std::forward<Args>(args)...); } // 初始化所有构造函数注册项 void init_constructors() { register_constructor<A, int>(Type::A); register_constructor<A, double>(Type::A); register_constructor<A, int, double>(Type::A); register_constructor<B, int, double>(Type::B); register_constructor<B, int, double, double>(Type::B); } // 使用示例 int main() { init_constructors(); auto a1 = make_Type(Type::A, 1); auto b1 = make_Type(Type::B, 2, 3.14); return 0; }
优缺点:扩展性强,新增类型或构造函数只需添加注册代码即可;但需要提前初始化函数表,且用到了std::type_index(仅用于区分参数签名,未用于类型判断)。
方案3:用std::variant打包参数(C++17+)
将参数打包为std::variant,利用std::visit和if constexpr在编译期过滤不合法的构造分支,同时保留运行时类型选择:
#include <memory> #include <iostream> #include <cassert> #include <variant> #include <tuple> enum class Type { A, B }; struct Base{}; struct A : public Base { A(const int a) { std::cout << "A::A(int)\n"; } A(double d) { std::cout << "A::A(double)\n"; } A(int a, double d) { std::cout << "A::A(int,double)\n"; } }; struct B : public Base { B(int i, double d) { std::cout << "B::B(int,double)\n"; } B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; } }; // 定义所有支持的参数组合变体 using ArgsVariant = std::variant< std::tuple<int>, std::tuple<double>, std::tuple<int, double>, std::tuple<int, double, double> >; std::shared_ptr<Base> make_Type(Type t, ArgsVariant args) { return std::visit([t](auto&& tuple_args) { return std::apply([t](auto&&... args) { if (t == Type::A) { if constexpr (std::is_constructible_v<A, decltype(args)...>) { return std::make_shared<A>(std::forward<decltype(args)>(args)...); } else { assert(false); return nullptr; } } else if (t == Type::B) { if constexpr (std::is_constructible_v<B, decltype(args)...>) { return std::make_shared<B>(std::forward<decltype(args)>(args)...); } else { assert(false); return nullptr; } } else { assert(false); return nullptr; } }, std::forward<decltype(tuple_args)>(tuple_args)); }, std::forward<ArgsVariant>(args)); } // 使用示例 int main() { auto a1 = make_Type(Type::A, ArgsVariant{std::make_tuple(1)}); auto b1 = make_Type(Type::B, ArgsVariant{std::make_tuple(2, 3.14)}); return 0; }
优缺点:结合了编译期检查和运行时选择的优点,无需编写大量重载;但需要预先定义所有支持的参数组合变体,新增参数组合时需更新ArgsVariant类型。
内容的提问来源于stack exchange,提问作者Arnab Mahanti
相关产品推荐
相关产品推荐

