Pure virtual interface for signal and slots leads to amibigiuos connect method
-
@jeremy_k
With the pure virtual interface I intend to force the inheriting class to implement/have these.
With "Right" I mean that I can connect the child signals&slots and they are correctly triggered.
Is such a mechanism even possible with moc generation?
Does moc generate for each *.cpp files, or does it respect inheritance?@SiGa said in Pure virtual interface for signal and slots leads to amibigiuos connect method:
@jeremy_k
With the pure virtual interface I intend to force the inheriting class to implement/have these.moc implements signals based on their signature. If it did support virtual signals, it would be creating the same implementation for each derived class.
The code is essentially:void signal(Types... args) { for (auto slot: connected_slots) slot(args); }
What's the point of an identical override of that?
With "Right" I mean that I can connect the child signals&slots and they are correctly triggered.
Right and correct are synonyms in this situation. To avoid a circular discussion, please use code examples.
Is such a mechanism even possible with moc generation?
With my current understanding of the problem, I'm going to say 'no', with the caveat that I don't think it is necessary.
Does moc generate for each *.cpp files, or does it respect inheritance?
moc is a relatively simplistic code generator. Given a file (.cpp or .h makes no difference) that mentions a signal or slot section, it generates code for signals and lookup tables for both. If moc never sees the file, no code is generated. If it finds no relevant keywords, no code is generated.
-
@JonB said in Pure virtual interface for signal and slots leads to amibigiuos connect method:
class IServerCommand : public QObject class LiveTab : public QWidget, IServerCommand
You are dual-inheriting from
QObject
(QWidget
obviously inherits it too), I thought that was not permitted (moc can't handle it for signals/slots)?It's the C++ diamond inheritance issue rather than, or perhaps in addition to moc's limitation.
-
@jeremy_k
The versions of QObject::connect that take a sender object are static. The code might as well invoke it that way.Do you mean that with specific calls to connect this might work?
@SiGa said in Pure virtual interface for signal and slots leads to amibigiuos connect method:
@jeremy_k
The versions of QObject::connect that take a sender object are static. The code might as well invoke it that way.Do you mean that with specific calls to connect this might work?
I only use QObject::connect(&sender, &SenderClass::signal, ...). I never use
&SenderClass::connect(...), or any other superclass of SenderClass. -
How about composition instead of inheritance?
class AbstractServerCommand : public QObject { Q_OBJECT public: virtual void ~AbstractServerCommand ( ) = default; signals: void requestServerCommandConnection(); public slots: void handleServerCommandResponse(); // Calls handleServerCommandResponseImpl private: void handleServerCommandResponseImpl() = 0; }; class IServerCommandProvider { public: virtual void ~IServerCommandProvider() = default; virtual AbstractServerCommand& getServerCommandInterface() = 0; }; class LiveTabServerResponse : public AbstractServerCommand { Q_OBJECT public: ~LiveTabServerResponse() override = default; private: void handleServerCommandResponseImpl() override; }; class LiveTab : public QWidget, IServerCommandProvider { Q_OBJECT public: ~LiveTab() override = default; // Co-variant overload LiveTabServerResponse& getServerCommandInterface() override; // Returns reference to m_ServerCommandInterface private: LiveTabServerResponse m_ServerCommandInterface;
Now you should have everything covered:
- If you derive from IServerCommandProvider, it forces you to provide access to a AbstractServerCommand
- The AbstractServerCommand defines the signal, and allows you to do a custom implementation of the slot
- If you need the same implementation for AbstractServerCommand in different classes, you only need to write it once
- If LiveTabServerResponse needs to call something in LiveTab, there are multiple ways to solve this. One way is to pass a std::function to the constructor of LiveTabServerResponse that e.g. should be called whenever the slot is called.
-
How about composition instead of inheritance?
class AbstractServerCommand : public QObject { Q_OBJECT public: virtual void ~AbstractServerCommand ( ) = default; signals: void requestServerCommandConnection(); public slots: void handleServerCommandResponse(); // Calls handleServerCommandResponseImpl private: void handleServerCommandResponseImpl() = 0; }; class IServerCommandProvider { public: virtual void ~IServerCommandProvider() = default; virtual AbstractServerCommand& getServerCommandInterface() = 0; }; class LiveTabServerResponse : public AbstractServerCommand { Q_OBJECT public: ~LiveTabServerResponse() override = default; private: void handleServerCommandResponseImpl() override; }; class LiveTab : public QWidget, IServerCommandProvider { Q_OBJECT public: ~LiveTab() override = default; // Co-variant overload LiveTabServerResponse& getServerCommandInterface() override; // Returns reference to m_ServerCommandInterface private: LiveTabServerResponse m_ServerCommandInterface;
Now you should have everything covered:
- If you derive from IServerCommandProvider, it forces you to provide access to a AbstractServerCommand
- The AbstractServerCommand defines the signal, and allows you to do a custom implementation of the slot
- If you need the same implementation for AbstractServerCommand in different classes, you only need to write it once
- If LiveTabServerResponse needs to call something in LiveTab, there are multiple ways to solve this. One way is to pass a std::function to the constructor of LiveTabServerResponse that e.g. should be called whenever the slot is called.
@Asperamanca said in Pure virtual interface for signal and slots leads to amibigiuos connect method:
public slots: void handleServerCommandResponse(); // Calls handleServerCommandResponseImpl private: void handleServerCommandResponseImpl() = 0;
This is an unnecessary indirection. Virtual slots are fine. They are not implemented by moc.
Qt uses virtual slots within public apis.
https://doc.qt.io/qt-6/qabstractitemview.html#resetvoid QAbstractItemView::reset() [virtual slot]
-
I don't see a way to get signals into
IServerCommand
. To solve the diamond problem (from the point-of-view of C++, not necessarily Qt) the common parent (i.e.QObject
) would need tovirtual
. I expect you do not want to change (and recompile) the Qt source so that QWidget uses virtual inheritance. This would leave the solution proposed by @Asperamanca.If there were a way to do this, the signals of
IServerCommand
don't have to bevirtual
.moc
will implement them for you and subclasses will just inherit their implementation. The implementation would not change for subclasses anyway. You could still connect using the name of the subclass.You have to be careful with connecting to your slots of
IServerCommand
and their subclasses. Under no circumstance should you writeQObject::connect(request, &Request::responseAvailable, command, &IServerCommand::handleServerCommandResponse);
If you write it like this C++ will not respect your
virtual
keyword (since you explicitly specified the implementation of which class to use). I hate to say this, but here you should use the old connect syntax (this is what it was meant for):QObject::connect(request, SIGNAL(responseAvailable()), command, SLOT(handleServerCommandResponse()));
If you want to stick to the new connect syntax, you are back to the other approach suggested by @Asperamanca:
class IServerCommand : public QObject { //... public slots: void handleServerCommandResponse(); // calls handleServerCommandResponseImpl private: virtual void handleServerCommandResponseImpl() = 0; }
In any case
class LiveTab : public QWidget, IServerCommand
is not a good idea as it is mixing concerns. A widget should do widget stuff and a command should do command stuff. The widget can know about the command and display its results. Think of the good old MVC pattern (though with Qt it is quite usable to mix View and Controller, but the Model should be kept separate). -
I don't see a way to get signals into
IServerCommand
. To solve the diamond problem (from the point-of-view of C++, not necessarily Qt) the common parent (i.e.QObject
) would need tovirtual
. I expect you do not want to change (and recompile) the Qt source so that QWidget uses virtual inheritance. This would leave the solution proposed by @Asperamanca.If there were a way to do this, the signals of
IServerCommand
don't have to bevirtual
.moc
will implement them for you and subclasses will just inherit their implementation. The implementation would not change for subclasses anyway. You could still connect using the name of the subclass.You have to be careful with connecting to your slots of
IServerCommand
and their subclasses. Under no circumstance should you writeQObject::connect(request, &Request::responseAvailable, command, &IServerCommand::handleServerCommandResponse);
If you write it like this C++ will not respect your
virtual
keyword (since you explicitly specified the implementation of which class to use). I hate to say this, but here you should use the old connect syntax (this is what it was meant for):QObject::connect(request, SIGNAL(responseAvailable()), command, SLOT(handleServerCommandResponse()));
If you want to stick to the new connect syntax, you are back to the other approach suggested by @Asperamanca:
class IServerCommand : public QObject { //... public slots: void handleServerCommandResponse(); // calls handleServerCommandResponseImpl private: virtual void handleServerCommandResponseImpl() = 0; }
In any case
class LiveTab : public QWidget, IServerCommand
is not a good idea as it is mixing concerns. A widget should do widget stuff and a command should do command stuff. The widget can know about the command and display its results. Think of the good old MVC pattern (though with Qt it is quite usable to mix View and Controller, but the Model should be kept separate).@SimonSchroeder Thanks, you said all I wanted to say here, in more detail
-
How about composition instead of inheritance?
class AbstractServerCommand : public QObject { Q_OBJECT public: virtual void ~AbstractServerCommand ( ) = default; signals: void requestServerCommandConnection(); public slots: void handleServerCommandResponse(); // Calls handleServerCommandResponseImpl private: void handleServerCommandResponseImpl() = 0; }; class IServerCommandProvider { public: virtual void ~IServerCommandProvider() = default; virtual AbstractServerCommand& getServerCommandInterface() = 0; }; class LiveTabServerResponse : public AbstractServerCommand { Q_OBJECT public: ~LiveTabServerResponse() override = default; private: void handleServerCommandResponseImpl() override; }; class LiveTab : public QWidget, IServerCommandProvider { Q_OBJECT public: ~LiveTab() override = default; // Co-variant overload LiveTabServerResponse& getServerCommandInterface() override; // Returns reference to m_ServerCommandInterface private: LiveTabServerResponse m_ServerCommandInterface;
Now you should have everything covered:
- If you derive from IServerCommandProvider, it forces you to provide access to a AbstractServerCommand
- The AbstractServerCommand defines the signal, and allows you to do a custom implementation of the slot
- If you need the same implementation for AbstractServerCommand in different classes, you only need to write it once
- If LiveTabServerResponse needs to call something in LiveTab, there are multiple ways to solve this. One way is to pass a std::function to the constructor of LiveTabServerResponse that e.g. should be called whenever the slot is called.
Thank you all for the suggestions.
In the end I implemented it as a QObject, which becomes a child of the parent, which contains the interface.
My mainframe then finds all instances of my ServerInterface through the QMetaMethod System, and connects everything accordingly
Surely not the purest C++ solution to this problem, but it seemed most logical to me.I don't know which reply I should mark as answer since I did not try out the suggestion from @Asperamanca
-
Thank you all for the suggestions.
In the end I implemented it as a QObject, which becomes a child of the parent, which contains the interface.
My mainframe then finds all instances of my ServerInterface through the QMetaMethod System, and connects everything accordingly
Surely not the purest C++ solution to this problem, but it seemed most logical to me.I don't know which reply I should mark as answer since I did not try out the suggestion from @Asperamanca
@SiGa There usually is more than one answer :-)
-
Thank you all for the suggestions.
In the end I implemented it as a QObject, which becomes a child of the parent, which contains the interface.
My mainframe then finds all instances of my ServerInterface through the QMetaMethod System, and connects everything accordingly
Surely not the purest C++ solution to this problem, but it seemed most logical to me.I don't know which reply I should mark as answer since I did not try out the suggestion from @Asperamanca
-