Qt et l’héritage de QObjects

Publié dans C / C++ | Marqué avec , , ,
Share

Le modèle objet de Qt est basé sur les QObject, qui offrent notamment le système très utile de communication inter-classes via signal / slot. Gérer l’héritage avec cette architecture peut-être de prime-abord légèrement tricky ! J’ai notamment eu quelques soucis de connexion signal / slot avec des classes héritant d’une interface (classe virtual pure).

Mon architecture

Une interface IMyClass et deux classes l’implémentant MyClass et MyClassSimulation. J’ai en prime un patron « factory » pour faciliter l’instanciation de ces classes (et pour avoir plus de souplesse dans l’avenir) : MyClassFactory.

C++ inheritance and factory

C++ inheritance and factory pattern

Je connecte alors la méthode « slot » de mon instance de IMyClass (qui est soit MyClass, soit MyClassSimulation en ait) :

connect(otherClass, SIGNAL(aSuperSignal()), myClassInstance, SLOT(myGreatMethod());

L’erreur typique

QObject::connect: Cannot connect OtherClass::aSuperSignal() to (null)::myGreatMethod()


Tout se passe comme si ma classe « IMyClass » n’était pas instanciée. Or, je sais qu’elle l’est ! En fait, la connexion signal / slot n’arrive pas à passer la barrière de l’héritage dans ce cas.
Encore une fois Stack Overflow m’a sauvé la mise avec cette question : Using Qt signals and slots with multiple inheritance.

La solution

La solution qui fonctionne est la suivante :

Add a QObject* asQObject() abstract method to MyInterface and implement it as { return this; } in all subclasses.

En somme : l’interface doit forcer l’existence d’un objet contenant l’instance réelle de la classe.
L’interface devient alors :

class IMyClass: public QObject
{
    Q_OBJECT
public:
    IMyClass(QObject * object);
    virtual ~IMyClass() {}
    virtual QObject *object() { return m_object; }

public slots:
    virtual void myGreatMethod()=0;

private:
    QObject *m_object;
}
#include "IMyClass.h"
IMyClass::IMyClass(QObject *object) : m_object(object) {}

Et un exemple typique d’implémentation de cette interface :

MyClass::MyClass()
    : IMyClass(this)
{
    // My default constructor
}

Et pour que tout fonctionne à merveille, il ne reste plus qu’à modifier la connexion signal / slot en conséquence :

connect(otherClass, SIGNAL(aSuperSignal()), myClassInstance.getObject(), SLOT(myGreatMethod());

Cela vous convient-il ? D’autres remarques ? Bon courage 😉

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *