23

我正在创建一个存储有关特定数据源的元数据的类。元数据以树的形式结构化,与 XML 的结构化方式非常相似。元数据值可以是整数、十进制或字符串值。

我很好奇 C++ 中是否有一种好方法可以为这种情况存储变体数据。我希望变体使用标准库,因此我避免使用可用的 COM、Ole 和 SQL VARIANT 类型。

我当前的解决方案如下所示:

enum MetaValueType
{
    MetaChar,
    MetaString,
    MetaShort,
    MetaInt,
    MetaFloat,
    MetaDouble
};

union MetaUnion
{
    char cValue;
    short sValue;
    int iValue;
    float fValue;
    double dValue;
};

class MetaValue
{
...
private:
    MetaValueType ValueType;
    std::string StringValue;
    MetaUnion VariantValue;
};

MetaValue 类具有各种 Get 函数,用于获取当前存储的变量值,但它最终使每个值查询成为 if/else if 语句的一大块,以确定我正在寻找的值。

我还探索了将值仅存储为字符串,并执行转换以获取不同的变体类型,但据我所知,这会导致一堆内部字符串解析和错误处理,这并不漂亮,打开使用浮点值解决了一大堆旧的精度和数据丢失问题,并且仍然不能消除上述 if/else if 问题的查询。

是否有人使用标准库实现或看到了更清洁的用于 C++ 变体数据类型的东西?

4

5 回答 5

33

从 C++17 开始,有std::variant.

如果您还不能使用它,您可能需要Boost.Variantstd::any(以及,C++17 之前的Boost.Any )提供了一种类似但不同的多态建模类型。

作为附加指针,您可以查找“<a href="https://stackoverflow.com/q/5450159/1968">类型擦除”。

于 2008-10-16T15:12:03.787 回答
13

虽然 Konrad 的答案(使用现有的标准化解决方案)肯定比编写自己的容易出错的版本更可取,但 boost 变体有一些开销,尤其是在复制构造和内存方面。

一种常见的定制方法是以下修改后的工厂模式:

  1. 为也封装对象类型(作为枚举)或使用“typeid”(首选)的通用对象创建 Base 接口。
  2. 现在使用模板Derived类实现接口。
  3. create使用带有签名的模板化函数创建工厂类:

template <typename _T> Base * Factory::create ();

这在内部Derived<_T>在堆上创建了一个对象,并重新调整了动态转换指针。专门针对您要实现的每个类进行此操作。

最后,定义一个Variant包含 thisBase *指针并定义模板 get 和 set 函数的包装器。getType(), , 赋值和相等运算符等实用功能isEmpty()可以在这里适当地实现。

根据实用功能和工厂实现,支持的类将需要支持一些基本功能,如赋值或复制构造。

于 2009-11-16T03:29:09.463 回答
9

您还可以使用更 C-ish 的解决方案,该解决方案将在您的系统上具有一个 double 大小的 void*,以及您正在使用的类型的枚举。它相当干净,但对于那些对系统的原始字节感到完全满意的人来说绝对是一个解决方案。

于 2008-10-16T15:30:15.513 回答
7

C++17 现在拥有std::variant您正在寻找的内容。

标准::变体

类模板 std::variant 表示类型安全的联合。在任何给定时间,std::variant 的实例要么保存其替代类型之一的值,要么在错误的情况下 - 没有值(这种状态很难实现,请参阅 valueless_by_exception)。

与联合一样,如果变体拥有某个对象类型 T 的值,则 T 的对象表示直接分配在变体本身的对象表示中。不允许变体分配额外的(动态)内存。

于 2017-02-13T18:08:12.147 回答
4

尽管这个问题已经回答了很长时间,但我想提一下Qt 库中的QVariant也可以做到这一点。

因为 C++ 禁止联合包含具有非默认构造函数或析构函数的类型,所以大多数有趣的 Qt 类不能在联合中使用。如果没有 QVariant,这对于 QObject::property() 和数据库工作等来说都是一个问题。

QVariant 对象一次保存单个 type() 的单个值。(某些 type() 是多值的,例如字符串列表。)您可以找出变体持有的类型 T,使用 convert() 将其转换为不同的类型,使用 toT 之一获取其值() 函数(例如,toSize())并检查是否可以使用 canConvert() 将类型转换为特定类型。

于 2015-04-15T06:44:09.370 回答