我正在创建一个存储特定数据源元数据的类。元数据是以树的形式构建的,与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++变体数据类型更干净的东西吗?
在C++17中,有std::variant
。
如果你还不能使用它,你可能需要Boost.Variant。std::any
提供了一种类似但不同的多态性建模类型(在C++17之前,还有Boost.Any)。
作为一个额外的指针,您可以查找"类型擦除"。
虽然Konrad的答案(使用现有的标准化解决方案)肯定比编写自己的易出错版本更可取,但boost变体有一些开销,尤其是在副本构建和内存方面。
一种常见的定制方法是以下修改的工厂模式:
- 为泛型对象创建一个Base接口,该接口还封装对象类型(可以是枚举),也可以使用"typeid"(首选)
- 现在使用模板
Derived
类实现该接口 - 创建一个带有签名的模板化
create
函数的工厂类:
template <typename _T> Base * Factory::create ();
这会在堆上内部创建一个Derived<_T>
对象,并重新运行一个动态强制转换指针。为您想要实现的每个类专门化这一点。
最后,定义一个包含此Base *
指针的Variant
包装器,并定义模板get和set函数。这里可以适当地实现getType()
、isEmpty()
、赋值和相等运算符等实用函数。
根据实用程序函数和工厂实现,支持的类将需要支持一些基本功能,如赋值或复制构造。
您还可以使用更具C风格的解决方案,该解决方案在您的系统上有一个双精度大小的void*,再加上您正在使用的类型的枚举。它相当干净,但对于那些对系统的原始字节感到完全满意的人来说,它绝对是一个解决方案。
C++17现在有了std::variant
,这正是您想要的。
std::变体
类模板std::variant表示一个类型安全的并集。一std::variant的实例在任何给定时间都保持一个值或在出现错误的情况下-没有值(此状态很难实现,请参见valueless_by_exception)。
与并集一样,如果变量持有某个对象类型T的值,则T的对象表示直接在对象内分配变体本身的表示。变体不允许分配额外的(动态)内存。
尽管这个问题已经回答了很长时间,但为了记录在案,我想提到Qt库中的QVariant也这样做。
因为C++禁止联合包含具有非默认值的类型构造函数或析构函数,最有趣的Qt类不能是用于工会。如果没有QVariant,这将是一个问题QObject::property()和用于数据库工作等的
一个QVariant对象一次保存一个类型()的单个值。(有些类型()是多值的,例如字符串列表。)你可以找出变体的类型T,将其转换为不同的使用convert()类型,使用toT()函数之一获取其值(例如,toSize()),并检查该类型是否可以转换为使用canConvert()的特定类型。