从非模板化接口检索'generic'数据

Retrieve 'generic' data from non-templated interface

本文关键字:generic 数据 检索 接口      更新时间:2023-10-16

目标是为数据创建一个通用容器,它允许我使用以下内容:

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来完成,但我在这一点上陷入了困境。

*代码的灵感来源于本文。

一个C++对象类似于boost::any,它可以在类似值的上下文中存储任何类型的数据,并且如果您提供完全相同的类型,就可以将其取出。

boost::any是类型擦除的一个例子,在这里,您"忘记"了关于类型的许多细节,只记得某些操作。在any的情况下,即复制、销毁并强制转换回同一类型。

在你的情况下,除了process和destroy,你已经忘记了一切。

运行时概念是该技术的另一个名称。

boost::variant是另一种方法,它存储类型列表中的一个,并提供"访问"所包含数据的方法。因为它知道它存储的类型列表,所以它可以键入传入的check函数对象,并确保它们可以使用存储的每个数据的类型。然后在运行时,它可以选择调用哪一个。


一般来说,若您启用了RTTI(许多编译器提供了删除它的选项),添加"强制转换回同一类型"相对容易。在您的情况下,只需在容器上对Derived<T>执行dyanmic_cast,如果有效,只需获取其中包含的_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);
}

上述方法将启用GenericContainer c; int* i = c.as<int>();,并且i为nullptr,如果c中没有int