0
votes

I want to create an interface for QObject class with signal/slot system.

The code below works but has a disadvantage: Interface inherits QObject and has no Q_OBJECT macro.

I want to make a pure virtual interface, so InterfaceClass shouldn't have a constructor. But in RealisationClass I cannot call QObject (error: type 'QObject' is not a direct base of 'RealisationClass') nor InterfaceClass (It hasn't constructor InterfaceClass(QObject* parent)) constructors. Moreover I cannot add Q_OBJECT macro in InterfaceClass (error: undefined reference to 'vtable' for InterfaceClass')

So, how should I change classes to add QObject() constructor in RealisationClass and add Q_OBJECT macro (or remove QObject inheritance) to 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(&everySecondTimer, &QTimer::timeout, interfacePointer.get(), &InterfaceClass::foo);
    everySecondTimer.start();
    return a.exec();
}

Edit

Q_OBJECT macro problem has another reason: I don't add InterfaceClass.h as target to Cmake's AUTOMOC (qmake (moc) didn't handle this file).

1
Which version of qt are you using? Works for me for Qt 5.15 and Qt 5.9. Please, try running qmake before building. Also, you don't need to use Q_OBJECT macro in the RealisationClass - Alloces
@Alloces Yeah, this is working code (I'am using Qt 5.12.9). But i can't call QObject(parent) constructor from RealisationClass. It worries me. About Q_OBJECT: "We strongly recommend the use of this macro in all subclasses of QObject regardless of whether or not they actually use signals, slots and properties, since failure to do so may lead certain functions to exhibit strange behavior." from here: doc.qt.io/qt-5/qobject.html - Nikxp
Yes, I understood that you need to call the QObject(parent) constructor. And it is strange behavior - I can create your RealisationClass with auto *r = new RealisationClass(myQObject) without any problem. And about Q_OBJECT - this is recommended when you inherit from the QObject class, but you inherit from the QObject inheritor class. For this case, I think, the most appropriate solution would be not inherit your InterfaceClass from QObject. - Alloces
Does InterfaceClass need to be a QObject? What if RealisationClass used multiple inheritance to derive from both InterfaceClass and QObject? - JarMan
I think yes, because of i want have a possibility to change 'QScopedPointer<InterfaceClass> interfacePointer(new RealisationClass);' to 'QScopedPointer<InterfaceClass> interfacePointer(new AnotherImplementationOfInterfaceClass);' and connect will be still working, As i understood there is a general advantage of using interfaces. I created class like @Dean Johnson says. But there is still some problems with signals. I think best way - create new question about it - Nikxp

1 Answers

1
votes

Using interfaces with QObject hierarchies is a real pain. You often want your interface to have some features of QObject (ie. signals/slots, destroyed signal, integrates with Qt APIs) but have it be a lightweight interface definition. Unless Qt is refactored so that an IQObject exists that is used throughout the Qt code-base, you can't get a perfect solution. What I recommend is to simply forward all constructor calls through to 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

With the using QObject::QObject line, InterfaceClass now has constructors with the same declarations as QObject. All they do is forward the call to QObject. With that, you can now do this in 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";};
};

This has two major downsides:

  1. Your interface is no longer a true interface because of the constructors.
  2. Your derived classes cannot inherit from multiple interfaces designed this way (diamond inheritance).

I still need to play around with virtual inheritance (class InterfaceClass : public virtual QObject) to see if that can solve #2 without introducing other issues.