wake-up-neo.com

Abstraktes Signal in Schnittstellenklasse deklarieren

Wie deklarieren Sie ein Qt-Signal in einer abstrakten Klasse/Schnittstelle, wenn die implementierende Klasse bereits von QObject/QWidget abgeleitet ist?

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();
}
28
Beachwalker

Wie ich in den letzten Tagen herausgefunden habe, ist die Art und Weise, wie Qt dies tut, folgendermaßen:

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

Jetzt können Sie eine Verbindung zu diesen Schnittstellensignalen herstellen.

Wenn Sie beim Herstellen der Verbindung zum Signal keinen Zugriff auf die Implementierung haben, ist für Ihre connect-Anweisung eine dynamische Umwandlung in QObject erforderlich:

IEmitSomething* es = ... // your implementation class

connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);

... und auf diese Weise sind Sie nicht gezwungen, die Implementierungsklasse Abonnenten und Clients zur Verfügung zu stellen. Ja!!!

45
Beachwalker

In Qt ist "Signale" Synonim für "geschützt". Aber es hilft dem MOC, den erforderlichen Code zu generieren. Wenn Sie also eine Schnittstelle mit einigen Signalen benötigen, sollten Sie sie als virtuelle abstrakt geschützte Methoden deklarieren. Der gesamte erforderliche Code wird von MOC generiert. Möglicherweise sehen Sie Details, dass "emit somesignal" durch den virtuellen Aufruf einer geschützten Methode mit demselben Namen ersetzt wird. Beachten Sie, dass der Körper von with method auch von Qt generiert wurde.

UPDATE: Beispielcode:

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

MeineImpl.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();
}
14
Dmitry Sazonov

Es gibt zwei Probleme bei der Deklaration von Signalen als abstrakte Methoden in Schnittstellen:

  1. Ein Signal ist nur ein Signal vom Standpunkt von Qt wenn es auf bestimmte Weise implementiert ist - und zwar wenn die Implementierung durch moc erzeugt wird und in den Metadaten für das Objekt enthalten ist.

  2. Es ist in der Regel ein schlechtes Design, Signale direkt von der Außenseite eines Objekts zu senden.

Da das Interface abstrakt ist, müssen Sie seine Signale nicht unbedingt deklarieren - es dient nur der Dokumentation der Absicht, da:

  1. Wenn ein Signal in einer Klasse implementiert ist, die von der Schnittstelle abgeleitet ist, können Sie das Metaobjektsystem verwenden, um seine Anwesenheit zu überprüfen.

  2. Du solltest diese Signalmethoden sowieso nicht direkt aufrufen.

  3. Nachdem Sie die Nicht-Objekt-Schnittstelle dynamisch in QObject umgewandelt haben, spielt es keine Rolle, dass die Implementierung von der Schnittstelle abgeleitet wurde.

Der einzig gültige Grund für solche Gymnastik wäre:

  1. Coax doxygen oder einen anderen Dokumentationsgenerator zur Dokumentation Ihres Codes.

  2. Erzwingen Sie die konkrete Klasse, eine Methode mit demselben Namen zu implementieren. Dies garantiert natürlich nicht, dass es sich tatsächlich um ein Signal handelt.

4
Kuba Ober

Wir wollen alle MOC endgültig loswerden, aber bis dies geschieht, möchte ich eine Alternative hinzufügen, die funktioniert, ohne QObject.h zu verwenden und ohne Q_OBJECT und Q_INTERFACE in der Interface-Klasse zu verwenden. 

Definieren Sie zunächst eine abstrakte Verbindungsfunktion in der Schnittstelle:

class I_Foo
{
public:
    virtual void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0;
};

Überschreiben Sie nun in der abgeleiteten Klasse die Funktion. Deklarieren Sie auch das Signal, fügen Sie Q_OBJECT usw. hinzu.

class Bar : public QObject, public I_Foo
{
    Q_OBJECT

public:
    void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);

signals:
    void A();
};

Dann machen die Klassen .cpp die Verbindung:

Bar::connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType void type)
{
    connect(this, SIGNAL(A()), receiver, method, type);
}

Der Nachteil ist, dass Sie die connect-Funktion in jede abgeleitete Klasse schreiben müssen, und Sie müssen den old-style-connect verwenden (oder möglicherweise eine Template-Funktion), aber das war es auch schon.

0
Bim