English 简体中文 Български

QtVariant

Introduction

In C++, a variable needs to have its type known at compile time. There are situations that however require us to deal with variables whose type is known only at run time. For example, let’s say you have a function that returns a value from the database. What would its return value be? In C, it would be a “void *” and an additional information would provide its type. Or assume that you allow the user to attach arbitrary information to the cells of your custom table widget. What type be the argument type for this function setUserCellData(type x)?

A QVariant can be used in the above situations and can be used to hold a value of any type. You can ask a QVariant for its type and handle the value stored inside it appropriately.

Before understanding Qt’s implementation, we will first try to see the various problems involved in designing the QVariant to appreciate the final solution better.

QVariant functionality requirements

  1. QVariant should not (and cannot) be a template class. If QVariant is a template, every class that store a QVariant needs to become a template.
  2. QVariant must work for both POD and non-POD data types. It should work for any type that is copyable and behaves as a value based type. Note that the support for non-POD types means that one cannot use a C union to hold all known types since C++ disallows placing non-POD types in a union.
  3. QVariant must be able to store custom user types. So, if you write a MyStruct, you should be able to place it in a QVariant.
  4. QVariant should (and cannot) not use C++ RTTI. In C++, RTTI only works with polymorphic classes.

Storing the value as ‘void *’

An idea would be to hold a ‘void *’ and a type inside QVariant. We can have a QVariant constructor as a function template that can just fit in any type. Like:

  1. class QVariant
  2. {
  3. private:
  4. union {
  5.     // POD data types
  6.     int i;
  7.     float f;
  8.     // All non-POD data types are stored as void *.
  9.     void *v;
  10. } data;
  11.  
  12.     enum DataType { Integer, Float, NonPod }; // the type of data
  13.     int type;
  14. public:
  15. // Constuctors for built-in/POD data types
  16. QVariant(int i)
  17. {
  18.     data.i = i;
  19.     type = Integer;
  20. }
  21.  
  22. // For all the non-POD types have a template constructor
  23. template <typename T>
  24. QVariant(const T &t)
  25. {
  26.     data.v = (void *) new T(t);
  27.     type = NonPod;
  28.     ...
  29. }

However, we will have trouble writing the destructor. How does one ‘delete’ the void pointer? In C++ you cannot delete a ‘void *’.

Arriving at the solution

As we can deduce from above, to delete the void * we simply need to know the type. And since QVariant needs to support user defined types it is not possible to put a huge switch case to reinterpret_cast the void * to a specific type and delete the pointer.

We also have a problem when trying to access the content of the QVariant. variant.value<MyStruct>() should not crash under any circumstance. If a conversion is not possible, it should return a default constructed MyStruct.

The solution is to have a system that can construct a value from a void *, destroy a void * and cast a void * type to access the value. If we have functions for these tasks for every type that can be stored in a QVariant, then QVariant can use these functions to work on the void *.

qMetaTypeConstructHelper and qMetaTypeDeleteHelper are the template construction helpers
qRegisterMetaType<MyStruct>(“MyStruct”) —- QMetaType::registerType(“MyStruct”, ctr, dtr); —- Internally it’s all stored in a QVector<QCustomTypeInfo>

If you do qRegisterMetaType for all types, we are done! But it’s a pain?
Q_DECLARE_METATYPE(Type) —— Create a class QMetaTypeId that provides qt_metatype_id() that registers on demand.

—- Provides a function

qMetaTypeId provides compile time look up of a type’s id

Categories: