C ++ вариант - PullRequest
       41

C ++ вариант

17 голосов
/ 16 октября 2008

Я нахожусь в процессе создания класса, который хранит метаданные о конкретном источнике данных. Метаданные структурированы в виде дерева, очень похожего на структуру 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, если проблема указана выше.

Кто-нибудь реализовал или видел что-нибудь более чистое для использования в варианте типа данных C ++ с использованием стандартных библиотек?

Ответы [ 5 ]

30 голосов
/ 16 октября 2008

Начиная с C ++ 17, std::variant.

Если вы еще не можете использовать это, вы можете Boost.Variant . Аналогичный, но отличный тип для моделирования полиморфизма предоставляется std::any (и, до C ++ 17, Boost.Any ).

Так же, как дополнительный указатель, вы можете искать « type erasure ».

12 голосов
/ 16 ноября 2009

Хотя ответ Конрада (с использованием существующего стандартизированного решения), безусловно, предпочтительнее, чем написание собственной версии, подверженной ошибкам, вариант форсирования имеет некоторые накладные расходы, особенно в области копирования и памяти.

Общий индивидуальный подход заключается в следующем измененном заводском образце:

  1. Создайте базовый интерфейс для универсального объекта, который также инкапсулирует тип объекта (либо в виде перечисления), либо с использованием 'typeid' (предпочтительно).
  2. Теперь реализуем интерфейс, используя шаблон Derived class.
  3. Создать фабричный класс с шаблонизированной create функцией с подписью:

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

Это внутренне создает объект Derived<_T> в куче и возвращает динамический указатель приведения. Специализируйте это для каждого класса, который вы хотите реализовать.

Наконец, определите оболочку Variant, которая содержит этот указатель Base * и определяет функции получения и установки шаблона. Здесь могут быть соответствующим образом реализованы такие служебные функции, как getType(), isEmpty(), операторы присваивания и равенства и т. Д.

В зависимости от функций утилит и реализации фабрики поддерживаемые классы должны будут поддерживать некоторые базовые функции, такие как назначение или копирование.

9 голосов
/ 16 октября 2008

Вы также можете перейти к более простому решению C-ish, у которого в вашей системе будет пустота * размером с двойное число плюс перечисление для того типа, который вы используете. Это достаточно чисто, но определенно решение для тех, кто чувствует себя полностью комфортно с необработанными байтами системы.

7 голосов
/ 13 февраля 2017

C ++ 17 теперь имеет std::variant, что именно то, что вы ищете.

станд :: вариант

4 голосов
/ 15 апреля 2015

Несмотря на то, что на этот вопрос давался ответ в течение длительного времени, для записи я хотел бы отметить, что QVariant также делает это.

...