Q与自己的类型工作进行比较

QVariant comparison with own types working?

本文关键字:比较 工作 类型 自己的      更新时间:2023-10-16

Update

我创建了一个qt错误票,希望文档能够扩展。

原始问题

相信 2010 年的问题和 Qt 文档,operator==()不适用于自定义类型。

报价:

bool QVariant:

:operator==(const QVariant & v) const

将此

QVariant 与 v 进行比较,如果它们相等,则返回 true;否则返回 false

QVariant使用它包含的 type() 的相等运算符来检查相等性。 QVariant将尝试convert() v其类型是否与此变体的类型不同。有关可能的转化列表,请参阅canConvert()

警告:此函数不支持向qRegisterMetaType()注册的自定义类型。

我试图重现 2010 年 Stackoverflow 问题中的重现案例,并且比较对我来说没有任何问题。

我还更进一步,尝试使用自己的类进行比较,该类也非常有效。若要重现,请将以下代码放入任何标头中:

enum MyEnum { Foo, Bar };
Q_DECLARE_METATYPE(MyEnum)
class MyClass
{
  int value;
public:
  MyClass() : value(0)
  {
  }
  MyClass(int a) : value(a)
  {
  }
  bool operator==(const MyClass &) const
  {
    Q_ASSERT(false); // This method seems not to be called
    return false;
  }
  bool operator!=(const MyClass &) const
  {
    Q_ASSERT(false); // This method seems not to be called
    return true;
  }
};
Q_DECLARE_METATYPE(MyClass)

并将以下代码放入任意函数:

QVariant var1 = QVariant::fromValue<MyEnum>(Foo);
QVariant var2 = QVariant::fromValue<MyEnum>(Foo);
Q_ASSERT(var1 == var2); // Succeeds!
var1 = QVariant::fromValue<MyEnum>(Foo);
var2 = QVariant::fromValue<MyEnum>(Bar);
Q_ASSERT(var1 != var2); // Succeeds!
QVariant obj1 = QVariant::fromValue<MyClass>(MyClass(42));
QVariant obj2 = QVariant::fromValue<MyClass>(MyClass(42));
Q_ASSERT(obj1 == obj2); // Succeeds!
obj1 = QVariant::fromValue<MyClass>(MyClass(42));
obj2 = QVariant::fromValue<MyClass>(MyClass(23));
Q_ASSERT(obj1 != obj2); // Succeeds!

我猜想在较新的 qt 版本中,当使用Q_DECLARE_METATYPE时会获取类型的大小,以便 QVariant 可以按字节比较未知类型的值。

但这只是一个猜测,我不想通过猜测qt的作用而不是依赖文档来冒险应用程序的稳定性。

我能知道,QVariant 如何比较未知类型吗?我更喜欢依赖规范而不是实现。

怕你需要依赖代码(而且,作为行为,它不能在不破坏的情况下更改),而不是文档。不过,下面有一个惊喜。

下面是相关代码。

对于具有未注册运算符的类型QVariant::operator==将只使用 memcmp .相关的代码片段(在 5.1 中)是这样的:

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 是用于执行类型感知操作的全局对象。它包含QVariant::Handler对象的数组;每个此类对象都包含指针,用于对它们知道如何处理的类型执行某些操作:

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;
};

这些成员中的每一个实际上都是指向函数的指针。

拥有这个全局对象数组的原因有点复杂 - 它允许其他Qt库(例如,QtGui)为这些库中定义的类型安装自定义处理程序(例如QColor)。

handlerManager上的operator[]将执行一些额外的魔术,即在给定类型的情况下获取正确的每个模块处理程序:

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

现在,该类型当然是自定义类型,因此此处返回的处理程序是Unknown模块。该Handler将使用 qvariant.cpp 中的 customCompare 函数,它这样做:

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));
}

除了一些错误检查和以特殊方式处理共享和空变体外,它还对内容使用memcmp

。仅当类型不是指针类型时,似乎才如此。想知道为什么那里有那个代码...


好消息!

从Qt 5.2开始,您可以使用QMetaType::registerComparator(见此处)使Qt调用operator<operator==自定义类型。只需添加到您的main

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

瞧,你会在你的相等运算符中命中断言。 现在QVariant::cmp是:

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);