Как поддерживать сравнения для объектов QVariant, содержащих настраиваемый тип?

Согласно документации Qt, QVariant::operator== не работает, как можно было бы ожидать, если вариант содержит настраиваемый тип:

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

Сравнивает этот QVariant с v и возвращает true, если они равны; в противном случае возвращает false.

В случае нестандартных типов их операторы равенства не вызываются. Вместо этого сравниваются адреса значений.

Как вы должны заставить это вести себя осмысленно для ваших пользовательских типов? В моем случае я сохраняю перечислимое значение в QVariant, например.

В шапке:

enum MyEnum { Foo, Bar };

Q_DECLARE_METATYPE(MyEnum);

Где-то в функции:

QVariant var1 = QVariant::fromValue<MyEnum>(Foo);
QVariant var2 = QVariant::fromValue<MyEnum>(Foo);
assert(var1 == var2); // Fails!

Что мне нужно сделать иначе, чтобы это утверждение было верным?

Я понимаю, почему это не работает - каждый вариант хранит отдельную копию перечисляемого значения, поэтому у них разные адреса. Я хочу знать, как я могу изменить свой подход к хранению этих значений в вариантах, чтобы либо это не было проблемой, либо чтобы они оба ссылались на одну и ту же базовую переменную.

Не думаю, что мне удастся обойтись без сравнения на равенство для работы. Контекст состоит в том, что я использую это перечисление как UserData в элементах в QComboBox, и я хочу иметь возможность использовать QComboBox::findData для поиска индекса элемента, соответствующего конкретному перечислимому значению.


person Tyler McHenry    schedule 19.05.2010    source источник
comment
Другой ответ: stackoverflow.com/questions/19703835 /   -  person peppe    schedule 18.03.2015


Ответы (3)


Очевидный ответ - выбросить данные из var1.value<MyEnum>() == var2.value<MyEnum>() для их сравнения, но для этого вам необходимо знать тип при сравнении. Похоже, в вашем случае это возможно.

Если вы просто используете перечисления, вы также можете преобразовать его в int для хранения в QVariant.

Изменить: для пояснения о поиске QComboBox, это использует модель поля со списком для поиска данных. В частности, он использует функцию match() _ 4_, чтобы проверить равенство. К счастью, эта функция виртуальная, поэтому вы можете переопределить ее в подклассе.

person Adam W    schedule 19.05.2010
comment
Очевидный ответ не работает, потому что я не могу изменить то, что QComboBox делает внутри своего findData метода. И да, я знаю, что могу просто сохранить его как int, но это кажется неэлегантным. Кроме того, пока я использую перечисление прямо сейчас, я, возможно, захочу использовать класс таким же образом позже, поэтому я ищу общее решение. - person Tyler McHenry; 20.05.2010
comment
Затем вы можете переопределить функцию match() (doc.qt.nokia.com/4.6/ qabstractitemmodel.html # match) в классе модели и назначьте его QComboBox, поскольку именно так он выполняет сравнения для findData Edit: см. здесь: qt.gitorious.org/qt/qt/blobs/4.7/src/gui/widgets / - person Adam W; 20.05.2010
comment
Это выглядит очень многообещающе. Я вернусь и приму этот ответ, если он сработает, как только у меня будет возможность попробовать. - person Tyler McHenry; 20.05.2010
comment
Сработало отлично! Я разделил QStandardItemModel на подклассы с классом, созданным по типу T, и повторно реализовал match так, чтобы, если искалось точное совпадение и искомый вариант был преобразован в T, метод match сравнивал .value<T>() вариантов, а не варианты самих себя. Спасибо! - person Tyler McHenry; 20.05.2010
comment
Рад, что это помогло, не так давно я понял, что QComboBox может ссылаться на QAbstractItemModel, это значительно упростило многие интерфейсы! - person Adam W; 20.05.2010
comment
Я думаю, что QComboBox - довольно недооцененный класс. У меня была точно такая же проблема. Они должны писать его большими буквами, поскольку он не назван, как другие классы представления (QxyzView). - person Kuba hasn't forgotten Monica; 22.08.2013

Попробуйте взломать qvariant, определите функцию по прототипу

typedef bool (*f_compare)(const Private *, const Private *);

и установите его в обработчик qvariant; Для работы с qvariant qt используйте Handler:

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

Этот пример демонстрирует, как взломать вывод отладки qvariant и преобразовать его в строку. Это очень простой пример, и вам нужно расширить его для своей задачи. «Идентификатор» - это мой настраиваемый тип.

class HackVariant : private QVariant
{
public:
     static void hackIt() {
         origh = handler;
         Handler* h = new Handler;
         *h = *origh;
         h->convert = convert;
         h->debugStream = hackStreamDebug;
         handler = h;
     }

private:
     static bool convert(const QVariant::Private *d, QVariant::Type t, void *result, bool *ok)
     {
         //qDebug() << Q_FUNC_INFO << "type:" << d->type;
         if (d->type >= QVariant::UserType)
         {
             QString& str = *((QString*)result);
             Identifier* ident = (Identifier*)(constData(d));
             str = ident->toString();
         }
         else
             return origh->convert(d, t, result, ok);
         return true;
     }

     static void hackStreamDebug(QDebug dbg, const QVariant &v) {
         if (v.canConvert<Identifier>())
             dbg << v.value<Identifier>();
         else
             origh->debugStream(dbg, v);
     }

     static const Handler* origh;

     static const void *constData(const QVariant::Private *d)
     {
         return d->is_shared ? d->data.shared->ptr : reinterpret_cast<const void *>(&d->data.ptr);
     }

};

Вам нужно создать функцию и установить ее как обработчик. Не забудьте перед использованием вызвать HackVariant::hackIt() в main.cpp (var1 == var2).

person strelok.ndv    schedule 15.08.2012

Решение для Qt 5

Qt поддерживает это из коробки, начиная с версии 5.2. См. QVariant :: operator == и QMetaType :: registerComparators .

Решение для Qt 4

Если вы все еще используете Qt 4 и пока не можете (или не хотите) обновляться до Qt 5, вы можете использовать CustomVariantComparator, который я написал для одного из своих проектов.

Вы можете использовать это следующим образом. Допустим, у нас есть класс Foo, который реализует operator== и должен использоваться в QVariant:

class Foo {
public:
    bool operator==(const Foo &other) { return ...; }
};
Q_DECLARE_METATYPE(Foo)

Затем просто поместите макрос Q_DEFINE_COMPARATOR рядом с реализацией Foo (т.е. в файле Foo.cpp, но не в файле Foo.h):

Q_DEFINE_COMPARATOR(Foo)

Затем, после создания вашего QApplication (или QCoreApplication) экземпляра, включите настраиваемый компаратор вариантов (это нужно сделать только один раз):

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    CustomVariantComparator::setEnabled(true);
    // more code...
}

Теперь следующий фрагмент кода будет работать должным образом (т.е. вызывать Foo::operator==).

QVariant::fromValue(Foo()) == QVariant::fromValue(Foo())
person emkey08    schedule 17.03.2015