Назначение класса
Поскольку C++ запрещает объединениям (union) включать типы, которые имеют конструкторы или деструкторы не по умолчанию, класс QVariant решает эту проблему иначе: он содержит единственное значение одного типа за раз. Это делает QVariant удобным контейнером для хранения значений произвольных типов, что особенно полезно при работе с динамическими данными, моделями, настройками и метаданными.
С помощью QVariant можно:
- узнать, какой тип содержит вариант — метод
type()или шаблонныйvalue<T>(); - преобразовать значение в другой тип — метод
convert(); - получить значение — один из методов
toT()(например,toInt(),toString(),toRect()); - проверить возможность преобразования — метод
canConvert().
Особенности методов toT()
Методы с именем toT() (например, toInt(), toString()) являются константными. Их поведение зависит от запрашиваемого типа:
- Если запрашивается хранимый тип — возвращается копия хранимого объекта.
- Если запрашивается тип, который может быть сгенерирован из сохранённого типа, —
toT()копирует и преобразует значение, оставляя сам объект без изменений.
Для большинства встроенных классов Qt предусмотрены встроенные конструкторы, поэтому преобразование часто проходит «незаметно». Однако если перепутать запрашиваемый класс, будет возвращено значение по умолчанию — без каких-либо предупреждений. Это главный источник ошибок при работе с QVariant.
Примеры создания
Рассмотрим несколько примеров инициализации QVariant значениями разных типов:
QVariant var1( 5 );
QVariant var2( "Test" );
QVariant var3( QRect( 1, 2, 3, 4 ) );
QVariant var4( 1.2 );
QVariant var5( QTime::currentTime() );
QVariant var6( QDateTime::currentDateTime() );
QVariant var7( QDate::currentDate() );
Здесь var1 хранит int, var2 — const char* (преобразуется в QString), var3 — QRect, var4 — double, а var5–var7 — типы даты и времени.
Пример с контейнером
QVariant умеет хранить и стандартные контейнеры Qt, что позволяет обходить их через итераторы:
QMap<int, QString> mapping;
mapping.insert(1, "One");
mapping.insert(2, "Two");
mapping.insert(3, "Three");
QVariant variant = QVariant::fromValue(mapping);
if (variant.canConvert<QVariantHash>()) {
QAssociativeIterable iterable = variant.value<QAssociativeIterable>();
QAssociativeIterable::const_iterator it = iterable.begin();
const QAssociativeIterable::const_iterator end = iterable.end();
for ( ; it != end; ++it) {
qDebug() << *it;
qDebug() << it.key();
qDebug() << it.value();
}
}
В результате будет выведено следующее:
QVariant(QString, "One")
QVariant(int, 1)
QVariant(QString, "One")
QVariant(QString, "Two")
QVariant(int, 2)
QVariant(QString, "Two")
QVariant(QString, "Three")
QVariant(int, 3)
QVariant(QString, "Three")
Обратите внимание: QAssociativeIterable позволяет работать с ассоциативными контейнерами (QMap, QHash) единообразно, не зная заранее конкретный тип контейнера.
Извлечение значений через toT()
Получить значения можно с помощью методов toT():
qDebug() << var1.toInt();
qDebug() << var2.toString();
qDebug() << var3.toRect();
qDebug() << var4.toDouble();
qDebug() << var5.toTime();
qDebug() << var6.toDateTime();
qDebug() << var7.toDate();
Эти методы не изменяют сам QVariant — они возвращают копию или преобразованное значение.
Извлечение значений через value<T>()
Альтернативный способ — шаблонный метод value<T>():
qDebug() << var1.value<int>();
qDebug() << var2.value<QString>();
qDebug() << var3.value<QRect>();
qDebug() << var4.value<double>();
qDebug() << var5.value<QTime>();
qDebug() << var6.value<QDateTime>();
qDebug() << var7.value<QDate>();
Разница между toT() и value<T>():
toT()— выполняет неявное преобразование, если тип не совпадает с хранимым;value<T>()— возвращает значение только если тип совпадает (в противном случае — значение по умолчанию), что делает его более строгим и предсказуемым.
Важные замечания
QVariantхранит только один тип за раз. При присваивании нового значения старое теряется.- Проверяйте тип перед извлечением. Используйте
canConvert<T>()илиtype(), чтобы избежать неожиданных значений по умолчанию:cppif (var.canConvert<int>()) { int value = var.toInt(); } value<T>()предпочтительнееtoT(), если известен ожидаемый тип — это снижает риск неявных преобразований и потери данных.- Для пользовательских типов требуется регистрация через
Q_DECLARE_METATYPE()и, при необходимости,qRegisterMetaType(). QVariant— основа метамодели Qt: используется вQAbstractItemModel,QMetaObject,QSettings,QSqlQueryи других подсистемах.
Итог
QVariant — универсальный контейнер Qt для хранения значений произвольных типов, созданный в обход ограничений C++ на объединения с нетривиальными конструкторами и деструкторами. Он предоставляет удобные методы для определения типа, проверки и выполнения преобразований, а также извлечения значений. Главное правило при работе с QVariant — всегда проверять тип перед извлечением, чтобы не получить молчаливое значение по умолчанию вместо ожидаемых данных.