C++状态机运行时修改变量模板类型的替代实现方案咨询
适用实现思路
运行时切换不同InnerClass<T>模板实例的核心是消弭不同模板实例之间的类型差异,除了公共虚基类的多态方案外,还有以下几种常用实现:
方案1:使用C++17 std::variant存储不同模板实例
适合所有可能的InnerClass模板参数类型在编译期可以完全枚举的场景,不需要修改原类定义,也不需要引入虚函数。
#include <variant> #include <cassert> // 原模板类无需任何修改 template <typename TemplateT> class InnerClass { public: void foo(); }; // 提前枚举所有可能的InnerClass实例类型 using InnerVariant = std::variant< InnerClass<ParticularType0>, InnerClass<ParticularType1>, InnerClass<ParticularType2> >; class StateMachine { public: InnerVariant inner; void someMethod() { // 统一调用不同实例的foo接口 std::visit([](auto&& impl) { impl.foo(); }, inner); } void changeState(int state) { switch(state) { case 0: inner.emplace<InnerClass<ParticularType0>>(); break; case 1: inner.emplace<InnerClass<ParticularType1>>(); break; case 2: inner.emplace<InnerClass<ParticularType2>>(); break; default: assert(false); } } };
- 优点:无虚函数调用开销,类型安全,不需要修改原有InnerClass的定义
- 缺点:所有可能的模板参数必须编译期提前确定,不能动态添加新类型,调用成员函数需要用
std::visit包装
方案2:手动类型擦除(无继承的轻量实现)
不想引入虚基类的继承层级时,可以手动封装接口做类型擦除,只暴露需要调用的公共操作:
#include <functional> #include <memory> #include <cassert> template <typename TemplateT> class InnerClass { public: void foo(); }; class StateMachine { private: // 存储实例指针和自定义销毁逻辑 std::unique_ptr<void, void(*)(void*)> inner = {nullptr, [](void*){}}; // 封装foo接口的调用逻辑 std::function<void()> foo_call; public: void someMethod() { if (foo_call) foo_call(); } template <typename T> void setInner() { auto* impl = new InnerClass<T>(); inner = {impl, [](void* p) { delete static_cast<InnerClass<T>*>(p); }}; foo_call = [impl]() { impl->foo(); }; } void changeState(int state) { switch(state) { case 0: setInner<ParticularType0>(); break; case 1: setInner<ParticularType1>(); break; case 2: setInner<ParticularType2>(); break; default: assert(false); } } };
- 优点:不需要修改InnerClass,也不需要继承,扩展性强,后续新增模板参数不需要修改StateMachine的类定义,只要在
changeState里加分支即可 - 缺点:要手动维护每个需要暴露的接口的包装函数,有
std::function的调用开销,类型安全依赖实现正确性
方案3:使用C++17 std::any 结合类型判定
如果编译期完全无法枚举所有可能的模板参数,可以用std::any存储实例,调用时做类型转换:
#include <any> #include <cassert> template <typename TemplateT> class InnerClass { public: void foo(); }; class StateMachine { public: std::any inner; void someMethod() { // 每新增一种模板参数都需要加对应判定分支 if (auto* p = std::any_cast<InnerClass<ParticularType0>>(&inner)) { p->foo(); } else if (auto* p = std::any_cast<InnerClass<ParticularType1>>(&inner)) { p->foo(); } else if (auto* p = std::any_cast<InnerClass<ParticularType2>>(&inner)) { p->foo(); } else { assert(false); } } void changeState(int state) { switch(state) { case 0: inner = InnerClass<ParticularType0>(); break; case 1: inner = InnerClass<ParticularType1>(); break; case 2: inner = InnerClass<ParticularType2>(); break; default: assert(false); } } };
- 优点:灵活性最高,完全不限定存储的类型
- 缺点:调用端要做大量类型判断,性能最差,类型安全性最低,容易漏分支出问题,非极端场景不推荐使用
方案选择建议
- 编译期能确定所有可能的模板参数优先选
std::variant方案 - 模板参数后续可能扩展、不想修改原有类结构选手动类型擦除方案
- 性能要求极高、调用接口固定的场景下,虚基类多态方案是最成熟易用的选择
内容的提问来源于stack exchange,提问作者KirillVNaumov
相关产品推荐
相关产品推荐

