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, 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 reference
Btw 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.
-
@Larvae
OOI, why do you think thatvoid *
is actually a preferable way to store the various possible types than theunion
you say it uses? The implication of your question is that a singlevoid *
which you then convert as needed is better, I'd rather have aunion
in this case in the first place. -
@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?
-
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.