Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.

QVariant Internals: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
(Decode HTML entity names)
(Formatting fixed)
 
(2 intermediate revisions by one other user not shown)
Line 1: Line 1:
{{Cleanup | reason=Auto-imported from ExpressionEngine.}}
'''English''' [[QtVariant SimplifiedChinese|简体中文]] [[QtVariant_Bulgarian|Български]]
[[Category:QtInternals]]
[[Category:QtInternals]]
[toc align_right="yes" depth="3"]
= QtVariant =
== Introduction ==
== 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)?
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.
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.
Line 21: Line 14:
# QVariant should (and cannot) not use C++ RTTI. In C+'', RTTI only works with polymorphic classes.
# QVariant should (and cannot) not use C++ RTTI. In C+'', RTTI only works with polymorphic classes.


== Storing the value as 'void '''' ==
== 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:
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:
<code>
<code>


Line 28: Line 21:
{
{
private:
private:
union {
union {
// POD data types
  // POD data types
int i;
  int i;
float f;
  float f;
// All non-POD data types are stored as void *.
  // All non-POD data types are stored as void *.
void '''v;
  void *v;
} data;
} data;


  enum DataType { Integer, Float, NonPod }; // the type of data
  enum DataType { Integer, Float, NonPod }; // the type of data
  int type;
  int type;
public:
public:
// Constuctors for built-in/POD data types
// Constuctors for built-in/POD data types
QVariant(int i)
QVariant(int i) {
{
  data.i = i;
data.i = i;
  type = Integer;
type = Integer;
}
}


// For all the non-POD types have a template constructor
// For all the non-POD types have a template constructor
template <typename T>
template <typename T>
QVariant(const T &t)
QVariant(const T &t) {
{
  data.v = (void*) new T (t);
data.v = (void''') new T (t);
  type = NonPod;
type = NonPod;
  //...
 
}
}
</code>
</code>


However, we will have trouble writing the destructor. How does one 'delete' the void pointer? In C''+ you cannot delete a 'void''''.
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 ==
== 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.
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.
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.
Line 68: Line 60:
qMetaTypeConstructHelper and qMetaTypeDeleteHelper are the template construction helpers
qMetaTypeConstructHelper and qMetaTypeDeleteHelper are the template construction helpers
qRegisterMetaType<MyStruct>("MyStruct")
qRegisterMetaType<MyStruct>("MyStruct")
QMetaType::registerType("MyStruct", ctr, dtr);
* QMetaType::registerType("MyStruct", ctr, dtr);
Internally it's all stored in a QVector<QCustomTypeInfo>
* Internally it's all stored in a QVector<QCustomTypeInfo>


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

Latest revision as of 13:49, 24 March 2016

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:

class QVariant
{
private:
 union {
  // POD data types
  int i;
  float f;
  // All non-POD data types are stored as void *.
  void *v;
 } data;

 enum DataType { Integer, Float, NonPod }; // the type of data
 int type;

public:
 // Constuctors for built-in/POD data types
 QVariant(int i) {
  data.i = i;
  type = Integer;
 }

 // For all the non-POD types have a template constructor
 template <typename T>
 QVariant(const T &t) {
  data.v = (void*) new T (t);
  type = NonPod;
 //...
}

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