C++ Variant

折月煮酒 提交于 2019-11-27 04:25:26

问题


I'm in the process of creating a class that stores metadata about a particular data source. The metadata is structured in a tree, very similar to how XML is structured. The metadata values can be integer, decimal, or string values.

I'm curious if there is a good way in C++ to store variant data for a situation like this. I'd like for the variant to use standard libraries, so I'm avoiding the COM, Ole, and SQL VARIANT types that are available.

My current solution looks something like this:

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;
};

The MetaValue class has various Get functions for obtaining the currently stored variant value, but it ends up making every query for a value a big block of if/else if statements to figure out which value I'm looking for.

I've also explored storing the value as only a string, and performing conversions to get different variant types out, but as far as I've seen this leads to a bunch of internal string parsing and error handling which isn't pretty, opens up a big old can of precision and data loss issues with floating point values, and still doesn't eliminate the query if/else if issue stated above.

Has anybody implemented or seen something that's cleaner to use for a C++ variant data type using standard libraries?


回答1:


As of C++17, there’s std::variant.

If you can’t use that yet, you might want Boost.Variant. A similar, but distinct, type for modelling polymorphism is provided by std::any (and, pre-C++17, Boost.Any).

Just as an additional pointer, you can look for “type erasure”.




回答2:


While Konrad's answer (using an existing standardized solution) is certainly preferable to writing your own bug-prone version, the boost variant has some overheads, especially in copy construction and memory.

A common customized approach is the following modified Factory Pattern:

  1. Create a Base interface for a generic object that also encapsulates the object type (either as an enum), or using 'typeid' (preferable).
  2. Now implement the interface using a template Derived class.
  3. Create a factory class with a templateized create function with signature:

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

This internally creates a Derived<_T> object on the heap, and retuns a dynamic cast pointer. Specialize this for each class you want implemented.

Finally, define a Variant wrapper that contains this Base * pointer and defines template get and set functions. Utility functions like getType(), isEmpty(), assignment and equality operators, etc can be appropriately implemented here.

Depending on the utility functions and the factory implementation, supported classes will need to support some basic functions like assignment or copy construction.




回答3:


You can also go down to a more C-ish solution, which would have a void* the size of a double on your system, plus an enum for which type you're using. It's reasonably clean, but definitely a solution for someone who feels wholly comfortable with the raw bytes of the system.




回答4:


C++17 now has std::variant which is exactly what you're looking for.

std::variant




回答5:


Although the question had been answered for a long time, for the record I would like to mention that QVariant also does this.



来源:https://stackoverflow.com/questions/208959/c-variant

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!