QVariant Internals: Difference between revisions
AutoSpider (talk | contribs) (Add "cleanup" tag) |
(Formatting fixed) |
||
(4 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
[[Category:QtInternals]] | [[Category:QtInternals]] | ||
== 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 | 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 15: | Line 8: | ||
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. | 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 == | |||
# 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. | # 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. | ||
# 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. | # 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. | ||
Line 22: | 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*" == | |||
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 | |||
<code> | <code> | ||
Line 30: | Line 21: | ||
{ | { | ||
private: | private: | ||
union { | union { | ||
// POD data types | |||
int i; | |||
float f; | |||
// All non-POD data types are stored as void *. | |||
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; | ||
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 & | QVariant(const T &t) { | ||
{ | data.v = (void*) new T (t); | ||
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 | 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 70: | 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); | |||
* 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. | |||
* 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
- 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.
- 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.
- 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.
- 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