利用虚基类函数从模板派生类提取数据的方案探讨
问题背景
我有一个Device对象,它可包含一个或多个State对象。为了不限制State能描述的状态类型,我把State的value设为模板类型。因为希望每个Device维护这些State对象的集合,让它们继承自GenericState类。
我希望通过GenericState指针读写模板化的value,但C++不支持模板虚函数。我的解决方案是在基类中声明纯虚的visitValue函数,它接收带void*参数的函数;模板派生类实现该虚函数,将State的value传入该函数,由调用者决定处理方式(类型转换、读写等)。
我的问题:
- 除了“不规范”和可读性差外,这种方法还有哪些问题?
- 是否有更优方法实现目标?我研究过
std::variant和std::any,但std::variant不够通用,std::any不合适;也考虑过静态/动态转换,但不确定开销。
相关代码
Device类(维护状态集合)
class Device { std::list<std::unique_ptr<GenericState>> states; ... }
GenericState和State类
class GenericState { public: std::string name; virtual ~GenericState() = default; virtual void visitValue(std::function<void (void*)> func) = 0; protected: GenericState(std::string name): name(name) {} }; template<typename T> class State: public GenericState { protected: T value; public: State(const std::string& name, const T& value): GenericState(name), value(value) {} const std::string getName() {return name; } const T getValue() { return value; } // Calls the provided function with a reference to the value for read/write void visitValue(std::function<void (void*)> func) override { func(&value); } };
GenericState指针读写State的value示例
State state = State(name, 5.0); GenericState* genericState = &state; // state.value = 5.0 double newVal = 0.2; genericState->visitValue([&newVal](void* val){*(double *)val = newVal;}); // state.value = 0.2 double testVal = 0.0; genericState->visitValue([&testVal](void* val){testVal = *(double *)val;}); // testVal = 0.2
解答
问题1:当前方法的其他问题
- 类型安全完全缺失:调用者必须精准知道
State的实际类型才能正确转换void*,一旦类型不匹配(比如把int*强转为double*),会直接触发未定义行为,这类错误编译期无法检测,只能在运行时暴露,排查难度极高。 - 非平凡类型处理风险:如果
T是需要特殊构造/析构逻辑的类型(比如带自定义析构函数的类、容器),直接通过void*操作会绕过这些逻辑,引发资源泄漏、状态不一致等问题。 - 额外性能开销:
std::function本身存在类型擦除、可能的堆分配等运行时开销,加上void*的间接访问,整体性能比直接操作值要差。 - 扩展性差:如果后续需要新增序列化、类型校验等操作,只能在
GenericState中新增虚函数,违反开闭原则,增加代码维护成本。
问题2:更优实现方案
方案1:基于std::any的类型安全改进
你觉得std::any不合适可能是没用到正确用法,它可以替代void*保证类型安全:
class GenericState { public: std::string name; virtual ~GenericState() = default; virtual std::any& getValue() = 0; virtual const std::any& getValue() const = 0; protected: GenericState(std::string name): name(name) {} }; template<typename T> class State : public GenericState { private: std::any value; public: State(const std::string& name, const T& val) : GenericState(name), value(val) {} std::any& getValue() override { return value; } const std::any& getValue() const override { return value; } // 类型安全的专属访问接口 T& getTypedValue() { return std::any_cast<T&>(value); } const T& getTypedValue() const { return std::any_cast<const T&>(value); } };
使用示例:
// 写操作 try { std::any_cast<double&>(genericState->getValue()) = 0.2; } catch(const std::bad_any_cast& e) { // 类型不匹配时的错误处理 } // 读操作 double testVal = std::any_cast<double>(genericState->getValue());
优势:编译期禁止隐式类型转换,运行时类型不匹配会抛出异常,彻底避免未定义行为;无需std::function的额外开销。
方案2:带类型标识的动态多态
在GenericState中存储类型信息(比如std::type_index),调用时先检查类型再转换:
class GenericState { public: std::string name; const std::type_index type; virtual ~GenericState() = default; protected: GenericState(std::string name, std::type_index t) : name(name), type(t) {} }; template<typename T> class State : public GenericState { private: T value; public: State(const std::string& name, const T& val) : GenericState(name, typeid(T)), value(val) {} T* getValuePtr() { return &value; } const T* getValuePtr() const { return &value; } };
使用示例:
if (genericState->type == typeid(double)) { auto* state = static_cast<State<double>*>(genericState); *state->getValuePtr() = 0.2; } else { // 类型不匹配的处理逻辑 }
优势:比void*更安全,类型检查明确;性能比std::any略好,没有std::any的类型擦除开销。
方案3:CRTP静态多态(编译期确定类型场景)
如果Device中的状态类型在编译期就能确定,无需完全动态多态,可以用CRTP实现静态多态,彻底避免虚函数开销:
template<typename Derived> class GenericState { public: std::string name; GenericState(std::string name) : name(name) {} template<typename Func> void visitValue(Func&& func) { static_cast<Derived*>(this)->visitValueImpl(std::forward<Func>(func)); } }; template<typename T> class State : public GenericState<State<T>> { private: T value; public: State(const std::string& name, const T& val) : GenericState<State<T>>(name), value(val) {} template<typename Func> void visitValueImpl(Func&& func) { func(value); } T& getValue() { return value; } const T& getValue() const { return value; } };
优势:无虚函数开销,性能最优;但仅适用于编译期就能确定所有状态类型的场景,无法混合存储不同类型的State。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

