14

アップデート

ドキュメントが拡張されることを期待して、 qt バグチケットを作成しました。

元の質問

2010年からの質問Qtドキュメントを信じて、カスタムタイプではoperator==()機能しません。

見積もり:

bool QVariant::operator==(const QVariant & v) const

この QVariant を比較し、等しい場合はv返します。trueそれ以外の場合は を返しますfalse

QVariant含まれている type() の等価演算子を使用して等価性をチェックします。そのタイプがこのバリアントのタイプと同じでない場合QVariantに試みます。可能な変換のリストについては、convert() vを参照してください。canConvert()

警告:この関数は、 に登録されたカスタム タイプをサポートしていませんqRegisterMetaType()

2010 年の Stackoverflow Question から再現ケースを再現しようとしましたが、比較は問題なく機能しました。

また、さらに一歩進んで、完全に機能する独自のクラスを使用して比較を試みました。再現するには、次のコードを任意のヘッダーに挿入します。

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 が未知の型をどのように比較するかを知ることはできますか? 実装よりも仕様に依存することを好みます。

4

1 に答える 1

27

ドキュメンテーションではなく、コードに依存する必要があるのではないかと思います (そして、動作であるため、変更せずに変更することはできません)。しかし、すぐ下に驚きがあります。

関連するコードは次のとおりです。

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 など) がそれらのライブラリ (fi QColor) で定義された型のカスタム ハンドラーをインストールできるようにするためです。

operator[]上のはhandlerManager、いくつかの追加の魔法を実行します。つまり、タイプを指定して適切なモジュールごとのハンドラーを取得します。

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

ここで型はもちろんカスタム型なので、ここで返される Handler はUnknownモジュールのものです。これは、これを行う関数 inHandlerを使用します。customCompareqvariant.cpp

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

これは、少しのエラー チェックと、共有バリアントと null バリアントを特別な方法で処理することを除けば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);
于 2013-11-04T21:39:08.740 に答える