Qt5 Tutorial QVariant and QMetaType - 2020
In this tutorial, we will learn about QVariant.
The Qt QVariant Class doc says:
The QVariant class acts like a union for the most common Qt data types.
Because C++ forbids unions from including types that have non-default constructors or destructors, most interesting Qt classes cannot be used in unions. Without QVariant, this would be a problem for QObject::property() and for database work, etc.
A QVariant object holds a single value of a single type() at a time. (Some type()s are multi-valued, for example a string list.) You can find out what type, T, the variant holds, convert it to a different type using convert(), get its value using one of the toT() functions (e.g., toSize()) and check whether the type can be converted to a particular type using canConvert().
The methods named toT() (e.g., toInt(), toString()) are const. If you ask for the stored type, they return a copy of the stored object. If you ask for a type that can be generated from the stored type, toT() copies and converts and leaves the object itself unchanged. If you ask for a type that cannot be generated from the stored type, the result depends on the type; see the function documentation for details.
The code below is doing serialization and QVariant is being used for the variable v.
#include <QCoreApplication> #include <QFile> #include <QDataStream> #include <QDebug> #include <QStringList> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "Writing..."; QFile file("C:/TEST/file.txt"); file.open(QIODevice::WriteOnly); QDataStream out(&file;); // we will serialize the data into the file QVariant v(123); // The variant now contains an int int x = v.toInt(); // x = 123 out << v; // Writes a type tag and an int to out qDebug() << v; v = QVariant("hello"); // The variant now contains a QByteArray v = QVariant(QObject::tr("hello")); // The variant now contains a QString int y = v.toInt(); // y = 0 since v cannot be converted to an int QString s = v.toString(); // s = tr("hello") (see QObject::tr()) out << v; qDebug() << v; file.flush(); file.close(); qDebug() << "Reading..."; file.open(QIODevice::ReadOnly); QDataStream in(&file;); // (opening the previously written stream) in >> v; // Reads an Int variant int z = v.toInt(); // z = 123 qDebug() << v; in >> v; // just checking the possibility // does not guarantee the valid conversion if(v.canConvert<QStringList>()) { qDebug() << v.toStringList(); } file.close(); return a.exec(); }
Output:
Writing... QVariant(int, 123) QVariant(QString, "hello") Reading... QVariant(int, 123) ("hello")
We did the serialization successfully!
A Note on GUI Types:
Because QVariant is part of the Qt Core module, it cannot provide conversion functions to data types defined in Qt GUI, such as QColor, QImage, and QPixmap.
Will the QVariant work with custom types such as class?
Let's look at the following code which want to get the class member from QVariant.
#include <QCoreApplication> #include "myclass.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyClass mClass; QVariant v = QVariant::fromValue(mClass); return a.exec(); }
In the code, we used QVariant QVariant::fromValue(const T & value).
It returns a QVariant containing a copy of value. Behaves exactly like setValue() otherwise.
As an example, we can use it this way:
MyCustomStruct s; return QVariant::fromValue(s);
When we compile it, we get the following message:
Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system.
So, it sounds like our class is not registered for meta-object system.
What is MetaType?
Let's look up the Qt doc, QMetaTeyp Class:
The QMetaType class manages named types in the meta-object system.
The class is used as a helper to marshall types in QVariant and in queued signals and slots connections. It associates a type name to a type so that it can be created and destructed dynamically at run-time. Declare new types with Q_DECLARE_METATYPE() to make them available to QVariant and other template-based functions.
Call qRegisterMetaType() to make type available to non-template based functions, such as the queued signal and slot connections.
Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.
The doc also provides some code to allocates and destructs an instance of MyClass::
int id = QMetaType::type("MyClass"); if (id != QMetaType::UnknownType) { void *myClassPtr = QMetaType::create(id); ... QMetaType::destroy(id, myClassPtr); myClassPtr = 0; }
So, looks like we need to register our class:
// myclass.h #ifndef MYCLASS_H #define MYCLASS_H #include <QMetaType> #include <QString> class MyClass { public: MyClass(); QString mName; }; Q_DECLARE_METATYPE(MyClass) #endif // MYCLASS_H
Here is the description about the MetaType declaration:
Q_DECLARE_METATYPE(Type)
This macro makes the type Type known to QMetaType as long as it provides a public default constructor, a public copy constructor and a public destructor. It is needed to use the type Type as a custom type in QVariant.
This macro requires that Type is a fully defined type at the point where it is used. For pointer types, it also requires that the pointed to type is fully defined. Use in conjunction with Q_DECLARE_OPAQUE_POINTER() to register pointers to forward declared types.
Ideally, this macro should be placed below the declaration of the class or struct. If that is not possible, it can be put in a private header file which has to be included every time that type is used in a QVariant.
Adding a Q_DECLARE_METATYPE() makes the type known to all template based functions, including QVariant. Note that if you intend to use the type in queued signal and slot connections or in QObject's property system, you also have to call qRegisterMetaType() since the names are resolved at runtime.
This example shows a typical use case of Q_DECLARE_METATYPE():
struct MyStruct { int i; ... }; Q_DECLARE_METATYPE(MyStruct)If MyStruct is in a namespace, the Q_DECLARE_METATYPE() macro has to be outside the namespace:
namespace MyNamespace { ... } Q_DECLARE_METATYPE(MyNamespace::MyStruct)Since MyStruct is now known to QMetaType, it can be used in QVariant:
MyStruct s; QVariant var; var.setValue(s); // copy s into the variant ... // retrieve the value MyStruct s2 = var.value<MyStruct>();
Back to our example.
If we run our code again, now it compiles successfully since we've been registered.
But we want to check if it really works. We set one member of the class and put the class into QVariant. Then, we try to get it back from QVariant.
Here is main.cpp:
#include <QCoreApplication> #include <QDebug> #include "myclass.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyClass mClass; mClass.name = "Debussy"; // put a class into QVariant QVariant v = QVariant::fromValue(mClass); // What's the type? // It's MyClass, and it's been registered // by adding macro in "myclass.h" MyClass vClass = v.value<MyClass>(); qDebug() << vClass.name; return a.exec(); }
Output:
"Debussy"
We got it.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization