QVariant comparison with own types working?

后端 未结 1 2121
迷失自我
迷失自我 2021-02-13 11:50

Update

I have created an qt bugticket hoping the documentation will be extended.

Original Question

Believing an Question from 2010 and the Qt Documen

相关标签:
1条回答
  • 2021-02-13 12:22

    I'm afraid you'll need to rely on the code (and, being behaviour, it can't be changed without breaking), and not documentation. There's a surprise just below, though.

    Here's the relevant code.

    QVariant::operator== for types with unregistered operators will just use memcmp. The relevant snippet (in 5.1) is this:

    bool QVariant::cmp(const QVariant &v) const
    {
        QVariant v1 = *this;
        QVariant v2 = v;
        if (d.type != v2.d.type) 
            // handle conversions....
    
        return handlerManager[v1.d.type]->compare(&v1.d, &v2.d);
    }
    

    handlerManager is a global object that gets used to perform type-aware manipulations. It contains an array of QVariant::Handler objects; each of such objects contains pointers to perform certain operations on the types they know how to handle:

    struct Handler {
        f_construct construct;
        f_clear clear;
        f_null isNull;
        f_load load;
        f_save save;
        f_compare compare;
        f_convert convert;
        f_canConvert canConvert;
        f_debugStream debugStream;
    };
    

    Each and every of those members is actually a pointer to a function.

    The reason for having this array of global objects is a bit complicated -- it's for allowing other Qt libraries (say, QtGui) to install custom handlers for the types defined in those libs (f.i. QColor).

    The operator[] on the handlerManager will perform some extra magic, namely get the right per-module handler given the type:

    return Handlers[QModulesPrivate::moduleForType(typeId)];
    

    Now the type is of course a custom type, so the Handler returned here is the one the Unknown module. That Handler will use the customCompare function in qvariant.cpp, which does this:

    static bool customCompare(const QVariant::Private *a, const QVariant::Private *b)
    {
        const char *const typeName = QMetaType::typeName(a->type);
        if (Q_UNLIKELY(!typeName) && Q_LIKELY(!QMetaType::isRegistered(a->type)))
            qFatal("QVariant::compare: type %d unknown to QVariant.", a->type);
    
        const void *a_ptr = a->is_shared ? a->data.shared->ptr : &(a->data.ptr);
        const void *b_ptr = b->is_shared ? b->data.shared->ptr : &(b->data.ptr);
    
        uint typeNameLen = qstrlen(typeName);
        if (typeNameLen > 0 && typeName[typeNameLen - 1] == '*')
            return *static_cast<void *const *>(a_ptr) == *static_cast<void *const *>(b_ptr);
    
        if (a->is_null && b->is_null)
            return true;
    
        return !memcmp(a_ptr, b_ptr, QMetaType::sizeOf(a->type));
    }
    

    Which, apart from a bit of error checking and handling shared and null variants in a special way, uses memcmp on the contents.

    ... only if the type is not a pointer type, it seems. Wonder why there's that code there...


    Good news!

    Starting with Qt 5.2, you can use QMetaType::registerComparator (see here) to make Qt invoke operator< and operator== on your custom type. Just add to your main:

    qRegisterMetaType<MyClass>();
    QMetaType::registerComparators<MyClass>();
    

    And voilà, you'll hit the assert in your equality operator. QVariant::cmp now is:

    QVariant v1 = *this;
    QVariant v2 = v;
    if (d.type != v2.d.type) 
        // handle conversions, like before
    
    // *NEW IMPORTANT CODE*
    if (v1.d.type >= QMetaType::User) {
        // non-builtin types (MyClass, MyEnum...)
        int result;
        // will invoke the comparator for v1's type, if ever registered
        if (QMetaType::compare(QT_PREPEND_NAMESPACE(constData(v1.d)), QT_PREPEND_NAMESPACE(constData(v2.d)), v1.d.type, &result))
            return result == 0;
    }
    // as before
    return handlerManager[v1.d.type]->compare(&v1.d, &v2.d);
    
    0 讨论(0)
提交回复
热议问题