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.



  • @Larvae

    IMHO because QVariant does not do a conversion, but simply supplies a different way of access to the same memory.



  • 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.


  • Qt Champions 2018

    @Larvae

    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 that void * is actually a preferable way to store the various possible types than the union you say it uses? The implication of your question is that a single void * which you then convert as needed is better, I'd rather have a union in this case in the first place.


  • Qt Champions 2018

    @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.

    @aha_1980

    1. 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.
    2. 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?


  • Moderators

    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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.