Declare Abstract Signal in Interface Class

Declare abstract signal in interface class

As I found out in the last days... the Qt way of doing this is like this:

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

Now you can connect to those interface signals.

If you don't have access to the implementation when connecting to the signal your connect statement will require a dynamic cast to QObject:

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

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

... and this way you're not forced to expose the implementation class to subscribers and clients. Yeah!!!

Declaring signals in interface class using Qt plugin system with new signal-slot syntax

Your main mistake is that you are combining projects that have circular dependencies.

I have restructured your project using the following structure:

test
├── test.pro
├── App
│   ├── App.pro
│   └── main.cpp
├── InterfacePlugin
│   ├── interfaceplugin_global.h
│   ├── interfaceplugin.h
│   └── InterfacePlugin.pro
└──Plugin
   ├── plugin_global.h
    ├── plugin.h
   └── Plugin.pro

In it we see that the Interface is an independent library. App and Plugin are 2 projects that use it.

The complete project can be found at the following link.

How to connect an abstract signal to a slot within the interface's constructor?

Generally speaking, interfaces should be abstract classes, and their constructors shouldn't be doing anything at all. So this is bad design.

If you insist on doing things such way, the semantics of C++ prevents us from doing it exactly as you state. When you multiply-inherit, a dynamic_cast to C will fail until the constructor of C is entered:

struct Interface {
Interface() { assert(dynamic_cast<QObject*>(this) == 0); }
};

struct C : public QObject, public Interface {
C() { assert(dynamic_cast<QObject*>(this)); }
};

So, we need some way of delaying the connection until the full object has been constructed, or at least until the QObject part of it is available.

A simple way would be for the interface to explicitly require the base to be constructed:

struct Interface {
Interface(QObject * base) {
connect(base, ...);
}
};

struct C : public QObject, public Interface {
C() : Interface(this) {}
};

The constructor signature nicely expresses the intent: Interface is meant to be used on classes deriving from QObject. It will not work on those that don't.

Another way is to delay the connection until the event loop has had a chance to run. This is acceptable if the connection isn't needed sooner than that. This doesn't require the Interface constructor to be passed an explicit pointer to the base class.

The timer is owned by the event dispatcher for the current thread, that way it won't leak even if the event loop isn't ever started.

class Interface {
public:
virtual void request() = 0; // entirely optional
Interface() {
auto timer = new QTimer(QAbstractEventDispatcher::instance());
timer.start(0);
QObject::connect(timer, &QTimer::timeout, [this, timer]{
timer.deleteLater();
connect(dynamic_cast<QObject*>(this), SIGNAL(request()), ...);
});
}
};

Please note that in all cases it's entirely superfluous to have the signal declared virtual in the interface. The Interface's constructor can check if the signal is present, and assert it, or even always abort().

Merely by declaring a virtual signal doesn't guarantee that the signal is actually a signal. The following will not work, even though the compiler provides no diagnostics to the contrary:

struct Interface {
signals:
virtual void aSignal() = 0;
};

struct Implementation : public QObject, public Interface {
void aSignal() {} // not really a signal!
};

This will detect the missing signal at runtime:

struct Interface {
// No need for virtual signal!
Interface(QObject * base) {
Q_ASSERT(base->metaObject()->indexOfSignal("request()") != -1);
}
};

struct Implementation : public QObject, public Interface {
Q_OBJECT
Q_SIGNAL void reuest(); // a typo
Implementation() : Interface(this) {} // will assert!
};

The best way to ensure that the virtual signal is followed could be to do both, and to declare the signal implementation as an override:

struct Interface {
virtual void aSignal() = 0;
Interface(QObject * base) {
Q_ASSERT(base->metaObject()->indexOfSignal("request()") != -1);
}
};

struct Implementation : public QObject, public Interface {
Q_OBJECT
Q_SIGNAL void request() Q_DECL_OVERRIDE;
Implementation() : Interface(this) {}
};

The compiler will catch typos even if you never instantiate the Implementation, and the interface will check at runtime that the implementation's signal is actually a signal, and not some other method.

It must also be noted that the signals: section of the Interface is bogus.

  1. signals is a preprocessor macro with empty expansion: to the compiler, it does nothing.

  2. Interface is not a QObject, and is thus ignored by the moc.

  3. signals: is only meaningful to moc iff it is in a class that both:

    • derives from QObject, and
    • contains the Q_OBJECT macro.

Generally speaking, virtual signals make little sense. They are all "virtual" in the sense that you can connect things without giving the compiler a virtual method.

Finally, they don't work with the Qt 5 compile-time-verified syntax either, since Interface is not a concrete QObject-deriving class with a proper signal

QT_INTERFACES and signal definitions

  1. Define signals as virtual abstract protected methods. (Instead of protected you may use signal: keyword).
  2. Final object must interhit your interface and QObject
  3. Cast pointer to interface to QObject * with dynamic_cast
  4. Connect pointer to necessary slots

You may check this topic for some solutions.

Connection of pure virtual signal of interface class

Since you know the derived type at compile type, you can connect to the proper, statically-known QObject-derived type. No need for dynamic casting or anything of the sort. You just don't want the listenToAnimal method to be available for non-AnimalInterface-inheriting types, though, even if it they have a compatible madeSound method:

C++11

#include <type_traits>

template< class T,
typename =
typename std::enable_if<std::is_base_of<AnimalInterface, T>::value>::type >
void listenToAnimal(T * animal) {
connect(animal, &T::madeSound, this, []{ qDebug() << "animal made sound"; });
}

C++03

template <class T>
void listenToAnimal(T * animal) {
Q_UNUSED(static_cast<AnimalInterface*>(animal));
connect(animal, &T::madeSound, this, &Widget::onAnimalMadeSound);
}

You can then use it without having to spell out the type - it's already known to the compiler:

listenToAnimal(dog_);
listenToAnimal(cat_);

If the derived type is not known at compile time, you have to dynamically cast to QObject and connect by name, not by method pointer. It will assert at runtime if you've passed in a wrong type - after all, it's not enough for it to be an instance of AnimalInterface, it also needs to be a QObject instance.

void listenToAnimal(AnimalInterface * animal) {
auto object = dynamic_cast<QObject*>(animal);
Q_ASSERT(object);
connect(object, SIGNAL(madeSound()), this, SLOT(onAnimalMadeSound()));
}

The fact that the type AnimalInterface has a virtual madeSound method is somewhat relevant - it guarantees that the derived class implements the method with such a signature. It doesn't guarantee that the method is a signal, though. So you should probably rethink your design and ask yourself: "What do I gain by using a static type system when I can't really use it for static type checking"?

Most likely you should make any methods that would nominally accept the AnimalInterface*, be parametrized and take a pointer to the concrete class. Modern code generators and linkers will deduplicate such code if type erasure leads to identical machine code.

Is it valid to define a pure virtual signal in C++/Qt?

  • Signals don't ever have an implementation[1] (i.e. you define the signal in your .h file and then there is no implementation in the .cpp).
  • The main purpose of declaring a function pure virtual is to force the inheriting class to provide an implementation.

Given the above two statements here's my thinking:

Signals don't have an implementation but declaring it pure virtual will require the inheriting class to provide an implementation... which directly conflict with "signals don't have an implementation". It's like asking someone to be in two places at once it's just not possible.

So in conclusion it seems like declaring a "pure virtual" "signal" should be an error and thus not valid.


In the case of an abstract base class here's what I think is correct:

When one declares the function only "virtual" it still gives the warning. To avoid any warnings I think the solution is to not qualify the signal with any "virtual" or "pure virtual" and then the inheriting class will not declare any signals but can still emit the signals defined in the base class.

[1] when I say that "signals don't ever have an implementation" I mean that the person implementing the class doesn't provide the implementation. I understand that behind the scene Qt's moc provides an implementation in the moc_FILE1.cpp .

Passing abstract class parameters by references to signals and slots

  1. Your class IBase must be copyable and constructable, if you want to pass it directly (not throught pointer)
  2. You need to register IBase * with Q_DECLARE_METATYPE macro in your header (only in global namespace) - Q_DECLARE_METATYPE( IBase * ). Pointers is a POD type, so they are copyable/constructable.
  3. If you want to pass IBase * between different threads, you need to register class with qRegisterMetaType<IBase *>() call;
  4. It is bad practice to pass pointers throught singals, because it is hard to control lifetime of passed objects.
  5. Good workaround: you may register your type with Q_DECLARE_METATYPE( IBase * ) macro and wrap your variable with QVariant: QVariant wrapper; wrapper.setValue( p ); p = wrapper.value<IBase *>();.


Related Topics



Leave a reply



Submit