目标是为数据创建一个通用容器,它允许我:
std::vector<GenericContainer> containerList = {"foo", 1.4f, 10, 'a'};
我写了以下代码*:
struct GenericContainer
{
template <typename DataType>
GenericContainer(DataType && data) :
_base(new Derived<DataType>(std::forward<DataType>(data)))
{ }
// operates with the data
void func()
{
_base->func();
}
struct Base
{
virtual void func() = 0;
virtual ~Base() = default;
};
template <typename T>
struct Derived : public Base
{
Derived(T && data) :
_data(std::forward<T>(data))
{}
void func() override
{
process(_data);
}
T _data;
};
Base * _base;
};
这里process
可以是一个自由函数,方便为每种类型定义。
void process(const float& f) { /**/ }
void process(const int& i) { /**/ }
// ...
我的问题:是否有优雅方法从GenericContainer恢复原始类型的数据?(不使用stringstream
之类的东西来处理数据)。
我的猜测是,可以通过将仿函数的某种模板struct
传递给GenericContainer
来完成,但此时我已经陷入困境。< / p>
*代码的灵感来自this article。
答案 0 :(得分:2)
可以在类似值的上下文中存储任何类型的数据的C ++对象,如果提供完全相同的类型,则可以将其取出,类似于boost::any
。
boost::any
是类型擦除的一个示例,您可以“忘记”有关类型的许多详细信息,并且只记住某些操作。在any
的情况下,即复制,销毁和强制转换为相同类型。
在你的情况下,你忘记了除process
之外的所有事情并销毁。
运行时概念是此技术的另一个名称。
boost::variant
是另一种方法,它存储一个类型列表,并提供“访问”所包含数据的方法。因为它知道它存储的类型列表,所以它可以对传入的函数对象进行类型检查,并确保它们可以使用存储的每个数据的类型。然后在运行时它可以选择要调用的那个。
通常,如果启用了RTTI(许多编译器都允许您删除它),则添加“cast-back-to-same-type”相对容易。在您的情况下,只需在容器上dyanmic_cast
执行Derived<T>
,然后如果有效,只需获取其中包含的_data
。这样的演员通常应该承认失败。
如果你知道你希望在那里的类型(或类型列表),这可以让你恢复它。如果您不知道类型,则只能运行在构造时擦除的代码。理论上,类型擦除数据可能来自完全独立于您想要在该类型上运行的代码编写的DLL;并且C ++不会为每个可执行文件提供编译器。
顺便说一句,你的GenericContainer(DataType && data)
有一个危险的签名;您正在转发引用,因此这可能意味着您的GenericContainer
可能最终存储对DataType的引用。通常,类型应该是值语义或引用语义;在它们之间静默切换类型将导致意外行为。
template<class T>
T* as() {
auto* d = dynamic_cast<Derived<T>*>(_base);
if (!d) return nullptr;
return std::addressof(d->_data);
}
template<class T>
T const* as() const {
auto const* d = dynamic_cast<Derived<T>*>(_base);
if (!d) return nullptr;
return std::addressof(d->_data);
}
如果c中没有GenericContainer c; int* i = c.as<int>();
,则上述方法将启用i
,int
为nullptr。