Как создать интерфейс для класса QObject

#qt #qt5

#qt #qt5

Вопрос:

Я хочу создать интерфейс для QObject класса с системой сигналов / слотов.

Приведенный ниже код работает, но имеет недостаток: интерфейс наследуется QObject и не имеет Q_OBJECT макроса.

Я хочу создать чисто виртуальный интерфейс, поэтому InterfaceClass у него не должно быть конструктора. Но в RealisationClass я не могу вызвать QObject (ошибка: тип ‘QObject’ не является прямой базой ‘RealisationClass’) ни InterfaceClass (у него нет конструктора InterfaceClass(QObject* parent) ) конструкторы. Кроме того, я не могу добавить Q_OBJECT макрос InterfaceClass (ошибка: неопределенная ссылка на ‘vtable’ для InterfaceClass’)

Итак, как мне изменить классы, чтобы добавить QObject() конструктор RealisationClass и добавить Q_OBJECT макрос (или удалить QObject наследование) в InterfaceClass.

IntefaceClass.h:

 #ifndef INTERFACE_CLASS_H
#define INTERFACE_CLASS_H
#include <QObject>

class InterfaceClass : public QObject {
    Q_OBJECT
 public:
  virtual ~InterfaceClass(){};
  virtual void foo() = 0;
};
Q_DECLARE_INTERFACE(InterfaceClass, "Interface")
#endif // INTERFACE_CLASS_H

 

RealisationClass.h:

 #include <QObject>
#include <QDebug>

#include "InterfaceClass.h"

class RealisationClass : public InterfaceClass {
  Q_OBJECT
  Q_INTERFACES(InterfaceClass)
 public:
    explicit RealisationClass(QObject* parent = nullptr){};
    void foo() override {qDebug() << "Hello, world";};
};
 

main.cpp:

 #include <QCoreApplication>

#include "RealisationClass.h"
#include "InterfaceClass.h"
#include <QObject>
#include <QTimer>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTimer everySecondTimer;
    everySecondTimer.setInterval(1000);
    QScopedPointer<InterfaceClass> interfacePointer(new RealisationClass);
    QObject::connect(amp;everySecondTimer, amp;QTimer::timeout, interfacePointer.get(), amp;InterfaceClass::foo);
    everySecondTimer.start();
    return a.exec();
}
 

Редактировать

Проблема с макросом Q_OBJECT имеет другую причину: я не добавляю InterfaceClass.h в качестве цели в AUTOMOC Cmake (qmake (moc) не обрабатывал этот файл).

Комментарии:

1. Какую версию qt вы используете? Работает для меня для Qt 5.15 и Qt 5.9 . Пожалуйста, попробуйте запустить qmake перед сборкой. Кроме того, вам не нужно использовать макрос Q_OBJECT в RealisationClass

2. @Alloces Да, это рабочий код (я использую Qt 5.12.9). Но я не могу вызвать конструктор QObject (parent) из RealisationClass. Меня это беспокоит. О Q_OBJECT: «Мы настоятельно рекомендуем использовать этот макрос во всех подклассах QObject независимо от того, используют ли они на самом деле сигналы, слоты и свойства, поскольку невыполнение этого требования может привести к странному поведению определенных функций». отсюда: doc.qt.io/qt-5/qobject.html

3. Да, я понял, что вам нужно вызвать конструктор QObject(родительский) . И это странное поведение — я могу создать ваш RealisationClass with auto *r = new RealisationClass(myQObject) без каких-либо проблем. И о Q_OBJECT — это рекомендуется, когда вы наследуете от класса QObject , но наследуете от класса-наследника QObject. В этом случае, я думаю, наиболее подходящим решением было бы не наследовать ваш InterfaceClass от QObject .

4. InterfaceClass Должен ли быть a QObject ? Что, если RealisationClass использовать множественное наследование для получения из обоих InterfaceClass и QObject ?

5. Я думаю, да, потому что я хочу иметь возможность изменить ‘QScopedPointer<InterfaceClass> interfacePointer ( new RealisationClass );’ на ‘QScopedPointer<InterfaceClass> interfacePointer ( new AnotherImplementationOfInterfaceClass ); и connect все еще будет работать, поскольку я понял, что есть общее преимущество использования интерфейсов. Я создал класс, как говорит @Dean Johnson. Но все еще есть некоторые проблемы с сигналами. Я думаю, что лучший способ — создать новый вопрос об этом

Ответ №1:

Использование интерфейсов с QObject иерархиями — настоящая боль. Вы часто хотите, чтобы ваш интерфейс имел некоторые функции QObject (например, сигналы / слоты, уничтоженный сигнал, интегрируется с Qt API), но должен быть упрощенным определением интерфейса. Если Qt не будет переработан так, чтобы IQObject существовал an, который используется во всей кодовой базе Qt, вы не сможете получить идеальное решение. Я рекомендую просто перенаправлять все вызовы конструктора на QObject :

 #ifndef INTERFACE_CLASS_H
#define INTERFACE_CLASS_H
#include <QObject>

class InterfaceClass : public QObject {
    Q_OBJECT
 public:
  using QObject::QObject; // <<--------

  virtual ~InterfaceClass(){};
  virtual void foo() = 0;
};
Q_DECLARE_INTERFACE(InterfaceClass, "Interface")
#endif // INTERFACE_CLASS_H
 

В using QObject::QObject строке InterfaceClass теперь есть конструкторы с теми же объявлениями, QObject что и . Все, что они делают, это перенаправляют вызов на QObject . Теперь вы можете сделать это в RealisationClass :

 #include <QObject>
#include <QDebug>

#include "InterfaceClass.h"

class RealisationClass : public InterfaceClass {
  Q_OBJECT
  Q_INTERFACES(InterfaceClass)
 public:
    explicit RealisationClass(QObject* parent = nullptr)
      : InterfaceClass(parent) // <<------
    {};
    void foo() override {qDebug() << "Hello, world";};
};
 

Это имеет два основных недостатка:

  1. Ваш интерфейс больше не является настоящим интерфейсом из-за конструкторов.
  2. Ваши производные классы не могут наследовать от нескольких интерфейсов, разработанных таким образом (алмазное наследование).

Мне все еще нужно поиграть с virtual inheritance ( class InterfaceClass : public virtual QObject ), чтобы посмотреть, может ли это решить # 2 без других проблем.

Комментарии:

1. Вы решили проблему сигналов в интерфейсе? Как я понял, нет способа создать виртуальный ( bugreports.qt.io/browse/QTBUG-41004 ). Поэтому, если вы добавите сигнал в интерфейс, вы не сможете добавить его в объявление RealisationClass.

2. В моем случае я создал невиртуальные сигналы как в InterfaceClass, так и в ReleaseClass. Есть 2 разных сигнала — это приводит к ошибкам. Чтобы избежать ошибок, я подключаю сигнал ReleaseClasses к сигналу InterfaceClasses в конструкторе ReleaseClass.

3. Не делайте их виртуальными — просто поместите сигнал непосредственно в интерфейс. Поскольку сигнал генерируется, moc вам не нужно его определять. Кроме того, никогда не было бы никаких причин переопределять сигнал, как то, что вы обычно делаете с виртуальными методами в интерфейсе. С сигналом в интерфейсе вам не нужно ничего помещать для сигнала в производный класс.