2013-10-31 28 views
9

更新

我已經創建了qt bugticket希望文檔將被擴展。與自己類型的QVariant比較工作?

原始的問題

相信一個Question from 2010Qt Documentation,該operator==()不與自定義類型的工作。

引用:

布爾的QVariant ::運算符==(常量的QVariant & v)的常量

比較此的QVariant與v並返回true它們是否相等;否則返回false

QVariant使用它包含的類型()的相等運算符來檢查相等性。 QVariant將嘗試convert()v如果它的類型與此變體的類型不同。有關可能的轉換列表,請參閱canConvert()

警告:此功能不支持使用qRegisterMetaType()註冊的自定義類型。

我試圖重現從Stackoverflow Question from 2010 repro案件和比較工作對我沒有任何問題。

我也走得更遠,並嘗試使用自己的類也完美比較比較。 要重現,把下面的代碼到任何標題:

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如何比較未知類型?我寧願依靠規範而不是執行。

+0

你叫'qRegisterMetaType()''上和MyEnum''MyClass'? – Zaiborg

+0

不,我沒有調用'qRegisterMetaType()'。 除了我上面發佈的代碼外,沒有使用MyClass或MyEnum的代碼行。 –

+0

您是否試圖將這些值轉換回其原始類型比較? ('QVariant :: value ()')也是什麼例如'var1'的類型? (或者要清楚,變量是什麼類型,它說的是什麼類型) – Zaiborg

回答

15

恐怕你需要依賴代碼(並且是行爲,不能無中斷地改變),而不是文檔。雖然下面有一個驚喜。

下面是相關的代碼。

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)爲這些庫中定義的類型(f.i. QColor)安裝自定義處理程序。

handlerManageroperator[]將執行一些額外的魔法,也就是讓每個模塊的處理程序是給定類型的權利:

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

現在的類型當然是一個自定義類型,所以處理器這裏返回是Unknown模塊之一。這Handler將使用qvariant.cppcustomCompare功能,做到這一點:

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(請參閱here)使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); 
+0

偉大和非常有用的答案!謝謝! –

+0

令人驚歎!我認爲,如果Qt今天誕生了,它將會更廣泛地使用模板。但編譯器的標準遵從性並不像今天那樣廣泛(即信號/時隙機制)。 –