C++17下基于模板类型擦除存储不同Foo实例的实现方案问询
可行方案说明
你要实现的异构Foo实例存储需求在C++17下有两种常用的落地方式,核心都是通过类型擦除抹平不同模板实例的类型差异:
方案1:基类派生实现类型擦除
适用于无法提前枚举所有Arg参数类型、需要灵活扩展的场景,类型检查在运行时完成。
#include <string> #include <functional> #include <unordered_map> #include <memory> #include <any> #include <stdexcept> // 非模板基类,做类型擦除的统一接口 struct FooBase { virtual ~FooBase() = default; // 统一调用入口,用std::any传递参数避免类型绑定 virtual void call(std::any arg_ptr) = 0; }; template <class Arg> struct Foo : FooBase { using arg_type = Arg; using fun_type = std::function< void(Arg&) >; fun_type fun; void call( Arg& arg ) { fun(arg); } // 实现基类的虚接口,做参数类型转换 void call(std::any arg_ptr) override { if (arg_ptr.type() != typeid(Arg*)) { throw std::bad_any_cast(); } fun(*std::any_cast<Arg*>(arg_ptr)); } }; struct Bar { using map_type = std::unordered_map<std::string, std::unique_ptr<FooBase>>; map_type map; // 插入指定参数类型的Foo实例 template<typename Arg> void insert(std::string name, std::function<void(Arg&)> fun) { auto foo = std::make_unique<Foo<Arg>>(); foo->fun = std::move(fun); map[std::move(name)] = std::move(foo); } // 按名称调用函数,参数类型不匹配会抛出std::bad_any_cast template<typename Arg> void call(const std::string& name, Arg& arg) { auto it = map.find(name); if (it == map.end()) { throw std::out_of_range("key not found"); } it->second->call(&arg); } // 主动获取指定类型的Foo实例,可用于直接操作Foo的其他成员 template<typename Arg> Foo<Arg>* get(const std::string& name) { auto it = map.find(name); if (it == map.end()) return nullptr; return dynamic_cast<Foo<Arg>*>(it->second.get()); } };
方案优缺点
- 优点:支持任意参数类型的
Foo实例,扩展性极强 - 缺点:需要堆内存分配,类型检查在运行时完成,有少量性能损耗
方案2:std::variant实现
适用于可以提前枚举所有可能用到的Arg参数类型的场景,类型检查基本在编译期完成,性能更高。
#include <string> #include <functional> #include <unordered_map> #include <variant> #include <stdexcept> template <class Arg> struct Foo { using arg_type = Arg; using fun_type = std::function< void(Arg&) >; fun_type fun; void call( Arg& arg ) { fun(arg); } }; // 提前枚举出所有支持的参数类型对应的Foo实例 using SupportedFooType = std::variant<Foo<int>, Foo<std::string>, Foo<double>>; struct Bar { using map_type = std::unordered_map<std::string, SupportedFooType>; map_type map; // 插入指定参数类型的Foo实例 template<typename Arg> void insert(std::string name, std::function<void(Arg&)> fun) { Foo<Arg> foo; foo.fun = std::move(fun); map[std::move(name)] = std::move(foo); } // 按名称调用函数,参数类型不匹配会抛出std::bad_variant_access template<typename Arg> void call(const std::string& name, Arg& arg) { std::get<Foo<Arg>>(map.at(name)).call(arg); } };
方案优缺点
- 优点:不需要堆内存分配,性能接近原生代码,类型错误大概率能在编译期发现
- 缺点:必须提前确定所有可能用到的参数类型,扩展性较差
补充说明
你原代码中operator[]直接返回auto&的需求无法直接实现:C++是静态类型语言,函数返回类型必须在编译期确定,而不同Foo<Arg>实例属于完全不同的类型,因此只能通过模板方法或者统一的擦除接口来实现访问逻辑。
内容的提问来源于stack exchange,提问作者Jonathan H
相关产品推荐
相关产品推荐

