IT Notes

Как пользоваться QVariant

QVariant является оберткой, в которую можно завернуть объект любого типа. Он представляет собой обходное решение для языка C++, который имеет строгую типизацию.

В самом QtSDK этот класс применяется там, где нежелательна привязка к конкретному типу:

  1. QSettings;
  2. Моделях;
  3. При работе с базами данных.

Принцип работы QVariant

Для большинства примитивных типов C++ и встроенных классов Qt предусмотрены конструкторы QVariant:

QVariant var1( 5 );
QVariant var2( "Hello" );
QVariant var3( QRect( 5, 5, 10, 10 ) );

Распаковать значения можно с помощью специальных функций-членов:

qDebug() << var1.toInt();    // --> 5
qDebug() << var2.toString(); // --> "Hello"
qDebug() << var3.toRect();   // --> QRect(5,5 10x10)

Другой способ заключается в использовании шаблонной функции value< T >():

qDebug() << var1.value< int >();     // --> 5
qDebug() << var2.value< QString >(); // --> "Hello"
qDebug() << var3.value< QRect >();   // --> QRect(5,5 10x10)

Если перепутать запрашиваемый при распаковке тип, то в большинстве случаев будет возвращено значение по умолчанию.

Шаблонная функция распаковки значений QVariant

Сначала лучше проверить, что состояние обертки в порядке и конвертация допустима. Можно использовать подобную вспомогательную шаблонную функцию:

template< typename T >
T unpack( const QVariant& var, const T& defVal = T() ) {
    if( var.isValid() && var.canConvert< T >() ) {
        return var.value< T >();
    }
    return defVal;
}

Если преобразование не удалось, то unpack() вернет предусмотренное значение по умолчанию. Например:

// В var2 находится QString --> возвращает значение по умолчанию
qDebug() << unpack( var2, QRect( 1, 1, 1, 1 ) ); // --> QRect(1,1 1x1)

Пользовательский тип в QVariant

Чтобы поместить в QVariant свой собственный класс MyClass, необходимо:

  1. Наличие в MyClass конструктора по умолчанию;
  2. Наличие в MyClass конструктора копирования;
  3. Наличие в MyClass деструктора;
  4. Объявление мета-типа с помощью макроса Q_DECLARE_METATYPE( MyClass ).

Пример такого класса:

class MyClass {
public:
    MyClass() : m_x( 0 ) { }
    MyClass( int x ) : m_x( x ) { }
    
    // Конструктор копирования и деструктор за нас создаст компилятор

    int get() const {
        return m_x;
    }

private:
    int m_x;
};

Q_DECLARE_METATYPE( MyClass )

Использовать его с QVariant не сложнее, чем со стандартными классами:

QVariant var4 = QVariant::fromValue( MyClass( 94 ) );
// Используем нашу функцию unpack()
qDebug() << unpack< MyClass >( var4 ).get(); // --> 94

Динамические структуры на основе QVariant

С помощью QVariant можно создавать удивительно гибкие классы:

class VariableContainer {
public:
    template< typename T >
    void set( const QString& key, const T& val ) {
        m_vals[ key ] = QVariant::fromValue( val );
    }

    template< typename T >
    T get( const QString& key, const T& defVal = T() ) {
        if( m_vals.contains( key ) ) {
            return unpack< T >( m_vals[ key ], defVal );
        }
        return defVal;
    }

private:
    QHash< QString, QVariant > m_vals;
};

VariableContainer позволяет определять динамические структуры с произвольным количеством и содержанием полей:

container.set( "A", 5 );
container.set( "B", 8.4 );
container.set( "C", QPoint( 3, 6 ) );

qDebug() << container.get< int >( "A" );    // --> 5
qDebug() << container.get< float >( "B" );  // --> 8.4
qDebug() << container.get< QPoint >( "C" ); // --> QPoint(3,6)
Понравилась статья?
Не забудь поделиться ей с друзьями!

Похожие публикации

Комментарии

Здравствуйте. Возникла необходимость в сериализации/десериализации сложных объектов в читаемом виде. Первое, что пришло в голову - XML Я использовал QDomDocument, у классов был реализован функции сериализации/десериализации, принимающие на вход QDomElement, для простых данных объект создавал атрибуты, если внутри объекта находился другой сложный объект, то создавался дочерний элемент и передавался в функцию сериализации этого объекта и т.д... Все вроде работает, но хотелось бы отвязать методы сериализации от конкретного формата (вдруг понадобится сохранить объекты в JSON, например) Вот сейчас пришла мысль использовать для сериализации QVariantMap. Функция сериализации будет возвращать VariantMap, заполненный данными объекта, кроме того, если внутри объекта будет другой сложный объект, то VariantMap сможет принять другой VariantMap в качестве значения, таким образом получая древовидную структуру, как и в предыдущем варианте. Но пока что возникает много сомнений, так как не до конца понимаю специфику работы QVariant. Как Вы думаете, пригоден ли QVariantMap для подобных задач?

Здравствуйте. Спасибо за комментарий.

Идея использования QVariantMap звучит вполне разумно, хотя полагаю, что можно обойтись и без этого. То, о чем Вы рассуждаете, очень близко по духу к паттерну Компоновщик. Можете ознакомиться с моими статьями на эту тему: Паттерн Компоновщик на C++, Паттерн Абстрактная фабрика на C++ и Паттерн Посетитель на C++.

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

Да, с этими статьями я уже ознакомился :) Нашел на этом сайте много полезного. Особое спасибо, за статью про организацию qt-проекта.

RSS RSS-рассылка

Популярное

Дешевый хостинг