使用虚拟基类函数从模板化派生类中提取数据
Extract data from templated derived class using virtual base class function
我有一个Device
对象,它可以有一个或多个State
对象。我不想限制State
对象可以描述什么样的状态,所以我已经将State
对象的value
模板化了。由于我希望每个Device
都保留这些State
对象的集合,所以我从GenericState
类派生了它们。
我希望能够与指向这个GenericState
类的指针进行交互,以便从模板化的值中读取和写入,但是不支持模板化的虚拟函数。我的解决方案是在基类中声明一个纯虚拟的"visiter"函数,该函数接受带有void*
参数的函数。模板化派生类实现虚拟函数,并使用值State
调用传入的函数。使用这种方法,我让调用者来选择如何处理值(强制转换为类型、读取、写入等(。下面的代码段。
我的问题是:
-
这种方法除了看起来"粗糙"且不是最可读之外,还有什么问题?
-
还有其他更好的方法来实现我的最终目标吗?我已经研究了
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
谢谢。
我的观察结果是,人们过度使用virtual
,可能是因为C++通常是如何教授的。如果您必须在电信系统中提供30年的前向兼容性和模块负载而不重新启动,virtual
非常有用;当具体类型已知时,尤其是在每次更改后重新编译整个项目时,它就不那么有用了。
这里需要注意的是,您将失去类型安全性给您的所有保证。在每次调用visitValue()
时,你都必须指定值的类型,因为void*
会隐藏它。类型安全的理念是,你应该进行类型检查(在类型规范的形式中称为"单元测试"(,如果你错过了,这些检查就会失败。现在,关键的观察是,你可能会以比State
s的数量更频繁的方式编写genericState->visitValue();
。因此,您可能只列出一次状态,例如在variant
中。
一旦你在variant
中有了它,你就可以简单地visit
它并有具体的类型——从那时起,你就有了类型安全性。所有这一切,你都赢得了列出所有状态一次。
using GenericStateVar = std::variant<State, State2, State3>;
GenericStateVar genericState(State(name, 5.0));
double newVal = 0.2;
std::visit([&](auto&& state) { // you might limit the capture here
state.setValue(newVal); // consider making value public
}, genericState);
double testVal = 0.0;
std::visit([&](auto&& state) { // you might limit the capture here
testVal = state.getValue(); // consider making value public
}, genericState);
variant
非常简单,不需要其他任何东西。此外,variant
往往比virtual
快一些,因为前者知道所有可能的类型(从技术上讲,它是switch
相对于函数指针(。
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 如何在新的派生对象中获取基本对象的数据?
- 为什么基类数据在派生类数据之前初始化
- 派生的 wxPanel 控件如何访问其中包含 wxDialog 中的数据?
- C++ 使用派生类方法更改基类数据成员
- 如何使虚函数接受仅在派生类中定义的数据类型?
- 继承的数据如何在派生类中放置(排列)?
- 如何使用数据成员填充派生类的对象到基类的指针数组中
- 如何使用派生类类型数据初始化 std::shared_ptr?
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- 如何在派生类上强制实现特定数据类型的构造函数?
- 以基类作为数据成员的派生类?
- 当算法需要派生类的知识时,将算法与数据解耦
- 派生数据类型与抽象数据类型
- 防止派生析构函数中的 vtable 数据争用
- 无法访问派生类函数内的基类的受保护数据成员
- 使用MPI/OpenMP的C 程序带有派生数据类型(嵌套类对象)容器
- 具有数据类型的C 抽象基础将在派生类中定义
- 具有灵活大小的结构的 MPI 派生数据类型
- 如何在MPI中从结构类型创建新的派生数据类型