Как объявить сигнал Qt в абстрактном классе / интерфейсе, когда реализующий класс уже получен из QObject / QWidget?
class IEmitSomething
{
public:
// this should be the signal known to others
virtual void someThingHappened() = 0;
}
class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
// signal implementation should be generated here
signals: void someThingHappended();
}
Как я узнал в последние дни … Qt способ сделать это так:
class IEmitSomething
{
public:
virtual ~IEmitSomething(){} // do not forget this
signals: // <- ignored by moc and only serves as documentation aid
// The code will work exactly the same if signals: is absent.
virtual void someThingHappened() = 0;
}
Q_DECLARE_INTERFACE(IEmitSomething, "IEmitSomething") // define this out of namespace scope
class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
Q_OBJECT
Q_INTERFACES(IEmitSomething)
signals:
void someThingHappended();
}
Теперь вы можете подключиться к этим интерфейсным сигналам.
Если у вас нет доступа к реализации при подключении к сигналу, вашему оператору соединения потребуется динамическое приведение к QObject
:
IEmitSomething* es = ... // your implementation class
connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);
… и таким образом вы не обязаны предоставлять класс реализации подписчикам и клиентам. Да уж!!!
В Qt «сигналы» являются синонимами слова «защищенный». Но это помогает MOC генерировать необходимый код. Итак, если вам требуется интерфейс с некоторыми сигналами — вы должны объявить их как виртуальные абстрактные защищенные методы. Весь необходимый код будет сгенерирован MOC — вы можете увидеть детали, что «emit somesignal» будет заменен виртуальным вызовом защищенного метода с тем же именем. Обратите внимание, что тело с методом aslo сгенерировано Qt.
ОБНОВИТЬ:
Образец кода:
MyInterfaces.h
#pragma once
struct MyInterface1
{
signals:
virtual void event1() = 0;
};
struct MyInterface2
{
signals:
virtual void event2() = 0;
};
MyImpl.h
#ifndef MYIMPL_H
#define MYIMPL_H
#include <QObject>
#include "MyInterfaces.h"
class MyImpl
: public QObject
, public MyInterface1
, public MyInterface2
{
Q_OBJECT
public:
MyImpl( QObject *parent );
~MyImpl();
void doWork();
signals:
void event1();
void event2();
};
class MyListner
: public QObject
{
Q_OBJECT
public:
MyListner( QObject *parent );
~MyListner();
public slots:
void on1();
void on2();
};
#endif // MYIMPL_H
MyImpl.cpp
#include "MyImpl.h"#include <QDebug>
MyImpl::MyImpl(QObject *parent)
: QObject(parent)
{}
MyImpl::~MyImpl()
{}
void MyImpl::doWork()
{
emit event1();
emit event2();
}
MyListner::MyListner( QObject *parent )
{}
MyListner::~MyListner()
{}
void MyListner::on1()
{
qDebug() << "on1";
}
void MyListner::on2()
{
qDebug() << "on2";
}
main.cpp
#include <QCoreApplication>
#include "MyImpl.h"
int main( int argc, char *argv[] )
{
QCoreApplication a( argc, argv );
MyImpl *invoker = new MyImpl( NULL );
MyListner *listner = new MyListner( NULL );
MyInterface1 *i1 = invoker;
MyInterface2 *i2 = invoker;
// i1, i2 - not QObjects, but we are sure, that they will be.
QObject::connect( dynamic_cast< QObject * >( i1 ), SIGNAL( event1() ), listner, SLOT( on1() ) );
QObject::connect( dynamic_cast< QObject * >( i2 ), SIGNAL( event2() ), listner, SLOT( on2() ) );
invoker->doWork();
return a.exec();
}
Есть две проблемы с объявлением сигналов как абстрактных методов в интерфейсах:
Сигнал — это сигнал только с точки зрения Qt когда реализовано определенным образом — а именно, когда реализация генерируется moc и включается в метаданные для объекта.
Обычно плохой дизайн — излучать сигналы непосредственно снаружи объекта.
Как следствие, поскольку интерфейс абстрактный, вам вообще не нужно объявлять его сигналы — он не служит никакой цели, кроме как для документирования намерений, поскольку:
Если сигнал реализован в классе, производном от интерфейса, вы можете использовать систему метаобъектов, чтобы проверить его наличие.
Вы не должны напрямую вызывать эти методы сигналов в любом случае.
Как только вы динамически приведете необъектный интерфейс к QObject
больше не имеет значения, что реализация была получена из интерфейса.
Единственными вескими причинами, оставшимися для такой гимнастики, будет:
Приведите коаксиальный Doxygen или другой генератор документации в документацию для вашего кода.
Вынудите конкретный класс иметь реализацию метода с тем же именем. Это, конечно, не гарантирует, что это на самом деле сигнал.