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)

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

Комментарии

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

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

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