Why does QVariant use a union?
-
Hi,
I'm currently looking into working with a QVariant's content without copying and went through the source code. While reading, I starte wondering:
Why does QVariant use a union to store it's data?
The union consists of the basic types (char, int, etc) , Object*, void* and PrivateShared* (a pointer to a shared struct containing another void*, i guess for implicit sharing).
I understand what a union is for, but why doesn't QVariant just use void*. It can be converted into any object or primitive. The type is stored by QVariant anyway since union can't do that. After reading the source code, using only a void* seems much simpler, you just have to add a cast and the QMetaType stuff.
-
Hi,
I'm currently looking into working with a QVariant's content without copying and went through the source code. While reading, I starte wondering:
Why does QVariant use a union to store it's data?
The union consists of the basic types (char, int, etc) , Object*, void* and PrivateShared* (a pointer to a shared struct containing another void*, i guess for implicit sharing).
I understand what a union is for, but why doesn't QVariant just use void*. It can be converted into any object or primitive. The type is stored by QVariant anyway since union can't do that. After reading the source code, using only a void* seems much simpler, you just have to add a cast and the QMetaType stuff.
-
Hi, also, for example in a 32-bit Windows Qt program, a void* cannot be converted into a double or a long long (you need 2 *void**s for those).
So by explicitly declaring all the types as members of that union, the compiler will make sure that sufficient # of bytes are allocated. -
I would call functions to "supply different ways of access to the same memory" as conversion, but then we would be splitting hairs.
void* doesn't need to have the same size as double or long long. Just cast it to double* and long long* and return *x to do this. It will return a copy, but since QVariant only returns copys and no references or pointers that wouldn't be a problem.
A quick'n'dirty example would be
class MyVariant { void* m_data; quint64 m_placeholder; size_t m_size; public: template <typename T> MyVariant(T &data) { qRegisterMetaType<Test>("Test"); m_size = sizeof (data); //m_data = (void*) malloc(m_size); T* tmp; if (m_size < sizeof (m_placeholder)) tmp = new(&m_placeholder) T; else tmp = new T; tmp = reinterpret_cast<T*>(data); m_data = tmp; } template <typename T> T data() { return reinterpret_cast<T>(m_data); } template <typename T> T* pointer() { return reinterpret_cast<T*>(&m_data); } template <typename T> T & ref() { static T *value; value = pointer<T>(); return *value; } };The quint64 is just to mimik the memory of the union in QVariant. Using a proper allocation could easily store objects of any size. With this, you could
MyVariant v(*something*); T t = v.data<T>(); // get a copy T *t = v.pointer<T>(); // get a pointer T& t = v.ref<T>(); // get a referenceBtw I'm working on a class inheriting from QVariant to add working with pointers and references instead of copies. Seems to work pretty good so far.
-
I would call functions to "supply different ways of access to the same memory" as conversion, but then we would be splitting hairs.
void* doesn't need to have the same size as double or long long. Just cast it to double* and long long* and return *x to do this. It will return a copy, but since QVariant only returns copys and no references or pointers that wouldn't be a problem.
A quick'n'dirty example would be
class MyVariant { void* m_data; quint64 m_placeholder; size_t m_size; public: template <typename T> MyVariant(T &data) { qRegisterMetaType<Test>("Test"); m_size = sizeof (data); //m_data = (void*) malloc(m_size); T* tmp; if (m_size < sizeof (m_placeholder)) tmp = new(&m_placeholder) T; else tmp = new T; tmp = reinterpret_cast<T*>(data); m_data = tmp; } template <typename T> T data() { return reinterpret_cast<T>(m_data); } template <typename T> T* pointer() { return reinterpret_cast<T*>(&m_data); } template <typename T> T & ref() { static T *value; value = pointer<T>(); return *value; } };The quint64 is just to mimik the memory of the union in QVariant. Using a proper allocation could easily store objects of any size. With this, you could
MyVariant v(*something*); T t = v.data<T>(); // get a copy T *t = v.pointer<T>(); // get a pointer T& t = v.ref<T>(); // get a referenceBtw I'm working on a class inheriting from QVariant to add working with pointers and references instead of copies. Seems to work pretty good so far.
Just cast it to double* and long long* and return *x to do this.
No, small values like int or double are directly stored in the
QVariant, there is no memore allocation. Memory allocation always have an overhead, therefore it absolutely makes sense to store small values directly.Out of curiosity: Why do you even care?
Regards
-
I care out of curiosity. I came accross the problem of accessing the data inside QVariant without copying. Since there is nothing in the documentation and I didn't find any posts I took a peek at the source code (qvariant.h, qvariant_p.h and qvariant.cpp) and wonderd why it's so complicated.
I know there is overhead for small values, it's just hard to tell how much, since QVariant does a lot of stuff on the side to handle itself.
The files together are nearly 6000 lines of code and like dozens of small scruct and class definitions. Of course a lot is just comments, but from looking I'd say it's less than 1/3. So I was wondering why something that doesn't seem so difficult is that complicated.
-
Hi,
I'm currently looking into working with a QVariant's content without copying and went through the source code. While reading, I starte wondering:
Why does QVariant use a union to store it's data?
The union consists of the basic types (char, int, etc) , Object*, void* and PrivateShared* (a pointer to a shared struct containing another void*, i guess for implicit sharing).
I understand what a union is for, but why doesn't QVariant just use void*. It can be converted into any object or primitive. The type is stored by QVariant anyway since union can't do that. After reading the source code, using only a void* seems much simpler, you just have to add a cast and the QMetaType stuff.
@Larvae
OOI, why do you think thatvoid *is actually a preferable way to store the various possible types than theunionyou say it uses? The implication of your question is that a singlevoid *which you then convert as needed is better, I'd rather have aunionin this case in the first place. -
I care out of curiosity. I came accross the problem of accessing the data inside QVariant without copying. Since there is nothing in the documentation and I didn't find any posts I took a peek at the source code (qvariant.h, qvariant_p.h and qvariant.cpp) and wonderd why it's so complicated.
I know there is overhead for small values, it's just hard to tell how much, since QVariant does a lot of stuff on the side to handle itself.
The files together are nearly 6000 lines of code and like dozens of small scruct and class definitions. Of course a lot is just comments, but from looking I'd say it's less than 1/3. So I was wondering why something that doesn't seem so difficult is that complicated.
@Larvae said in Why does QVariant use a union?:
I know there is overhead for small values, it's just hard to tell how much,
Then I recommend you to look into
malloc()source code and you will begin to understand... ;) -
@Larvae said in Why does QVariant use a union?:
I know there is overhead for small values, it's just hard to tell how much,
Then I recommend you to look into
malloc()source code and you will begin to understand... ;)@JonB
I didn't mean it like void* is better. I meant why use union over the other if you want to use it with types that are not part of the union anyway? If I would design this, I would either a union and stick with those types (maybe like in boost::variant) or not a union at all if I work with types that aren't in the union. At least based on my current knowledge. But since Qt did it the other way I wanted to know why and hopefully learn something.- placement new like in my example has far less overhead using the preallocated memory than a simple malloc. And since QVariant allways copies the data there are also allocations for the union (indirectly through the class) anyway.
- I didn't mean the overhead for the malloc() alone but for the whole design. Like I said, there are a lot of helper classes and structs adding to the overhead. E.g. would a QVariant using only void* also need that many helpers or even more? How would the whole design differ in efficiency?
Also, why does the union hold a void* and a pointer to a struct with another void* for implicit sharing? The union is already in a private type, why isn't it used for that directly instead of yet another element?
-
@JonB
I didn't mean it like void* is better. I meant why use union over the other if you want to use it with types that are not part of the union anyway? If I would design this, I would either a union and stick with those types (maybe like in boost::variant) or not a union at all if I work with types that aren't in the union. At least based on my current knowledge. But since Qt did it the other way I wanted to know why and hopefully learn something.- placement new like in my example has far less overhead using the preallocated memory than a simple malloc. And since QVariant allways copies the data there are also allocations for the union (indirectly through the class) anyway.
- I didn't mean the overhead for the malloc() alone but for the whole design. Like I said, there are a lot of helper classes and structs adding to the overhead. E.g. would a QVariant using only void* also need that many helpers or even more? How would the whole design differ in efficiency?
Also, why does the union hold a void* and a pointer to a struct with another void* for implicit sharing? The union is already in a private type, why isn't it used for that directly instead of yet another element?
Hi @Larvae, you might get better answers to questions about internal code and design decisions at the Interest mailing list (subscribe first, then post). Qt engineers are active on that list; this forum is mainly used by Qt users who don't necessarily know any internal details.