web-dev-qa-db-fra.com

Déclarer un signal abstrait dans la classe d'interface

Comment déclarer un signal Qt dans une classe/interface abstraite lorsque la classe d'implémentation est déjà dérivée de 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();
}
28
Beachwalker

Comme je l'ai découvert au cours des derniers jours ... la façon de procéder de Qt est la suivante:

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

Vous pouvez maintenant vous connecter à ces signaux d'interface.

Si vous n'avez pas accès à l'implémentation lors de la connexion au signal, votre déclaration de connexion nécessitera un transtypage dynamique en QObject:

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

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

... et ainsi, vous n'êtes pas obligé d'exposer la classe d'implémentation aux abonnés et aux clients. Ouais!!!

45
Beachwalker

Dans Qt, "signaux" est synonyme de "protégé". Mais cela aide MOC à générer le code nécessaire. Donc, si vous avez besoin d'une interface avec certains signaux, vous devez les déclarer en tant que méthodes protégées abstraites virtuelles. Tous les codes nécessaires seront générés par MOC - vous pourrez voir des détails, que "emit somesignal" sera remplacé par un appel virtuel de méthode protégée avec le même nom. Notez que le corps de with méthode est également généré par Qt.

UPDATE: Exemple de code:

MesInterfaces.h

#pragma once

struct MyInterface1
{
signals:
    virtual void event1() = 0;
};

struct MyInterface2
{
signals:
    virtual void event2() = 0;
};

MonImpl.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

MonImpl.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

La déclaration de signaux en tant que méthodes abstraites dans les interfaces pose deux problèmes:

  1. Un signal est un signal du point de vue de Qt uniquement lorsqu'il est implémenté d'une manière particulière - à savoir, lorsque l'implémentation est générée par moc et est incluse dans les métadonnées de l'objet.

  2. Il est généralement mauvais d’émettre des signaux directement de l’extérieur d’un objet.

En corollaire, l'interface étant abstraite, vous n'avez pas vraiment besoin de déclarer ses signaux. Elle n'a pas d'autre fonction que de documenter l'intention, puisque:

  1. Si un signal est implémenté dans une classe dérivée de l'interface, vous pouvez utiliser le système métaobjet pour vérifier sa présence.

  2. De toute façon, vous n'êtes pas censé appeler directement ces méthodes de signal.

  3. Une fois que vous avez dynamisé l'interface non-objet en QObject, il n'importe plus que l'implémentation dérive de l'interface.

Les seules raisons valables pour une telle gymnastique seraient les suivantes:

  1. Coax doxygen ou un autre générateur de documentation à fournir une documentation pour votre code.

  2. Forcer la classe concrète à implémenter une méthode portant le même nom. Cela ne garantit évidemment pas qu'il s'agisse d'un signal.

4
Kuba Ober

Nous voulons tous nous débarrasser définitivement du MOC, mais en attendant, je veux ajouter une alternative qui fonctionne sans inclure QObject.h et sans utiliser Q_OBJECT et Q_INTERFACE dans la classe d'interface. 

Commencez par définir une fonction de connexion abstraite dans l'interface:

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

Maintenant, dans la classe dérivée, remplacez la fonction. Déclarez également le signal, ajoutez Q_OBJECT, etc.

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

Ensuite, à l'intérieur de cette classe .cpp, connectez-vous:

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

La mise en garde est que vous devez écrire la fonction connect dans chaque classe dérivée et que vous devez utiliser old-style-connect (ou peut-être utiliser une fonction template), mais c'est à peu près tout.

0
Bim