#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
withauto *r = new RealisationClass(myQObject)
без каких-либо проблем. И о Q_OBJECT — это рекомендуется, когда вы наследуете от класса QObject , но наследуете от класса-наследника QObject. В этом случае, я думаю, наиболее подходящим решением было бы не наследовать вашInterfaceClass
от QObject .4.
InterfaceClass
Должен ли быть aQObject
? Что, если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";};
};
Это имеет два основных недостатка:
- Ваш интерфейс больше не является настоящим интерфейсом из-за конструкторов.
- Ваши производные классы не могут наследовать от нескольких интерфейсов, разработанных таким образом (алмазное наследование).
Мне все еще нужно поиграть с 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
вам не нужно его определять. Кроме того, никогда не было бы никаких причин переопределять сигнал, как то, что вы обычно делаете с виртуальными методами в интерфейсе. С сигналом в интерфейсе вам не нужно ничего помещать для сигнала в производный класс.