You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

利用虚基类函数从模板派生类提取数据的方案探讨

问题背景

我有一个Device对象,它可包含一个或多个State对象。为了不限制State能描述的状态类型,我把State的value设为模板类型。因为希望每个Device维护这些State对象的集合,让它们继承自GenericState类。

我希望通过GenericState指针读写模板化的value,但C++不支持模板虚函数。我的解决方案是在基类中声明纯虚的visitValue函数,它接收带void*参数的函数;模板派生类实现该虚函数,将State的value传入该函数,由调用者决定处理方式(类型转换、读写等)。

我的问题:

  1. 除了“不规范”和可读性差外,这种方法还有哪些问题?
  2. 是否有更优方法实现目标?我研究过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 12:48:29