📜 ⬆️ ⬇️

Crutch for the signal-slot system in Qt



Hello to all. I want to tell you about this ... About two weeks ago, for the first time, I had to work with GUI using C ++ and, after a little googling, I decided to use Qt. He was all terribly praised, and indeed at first glance he looked very dignified.

At a second glance, Qt turned out to be a good fit too, but due to some limitations in the implementation of its method-object compiler, I had to construct small crutches. In this essay (I think the word “essay” will fit better, because it doesn’t draw on the article in terms of volume) I want to talk about how I solved the problems that arise.
')
Added: Based on the comments of good people, an edit was made that tells how you can do without MOC and without crutches at all.



How it all began



It all started with the fact that I needed to make a template controller class as part of the implementation of the MVC architecture in my library. The controller had to interact with heterogeneous integer data, associating QSpinBox with them as a GUI. If we discard any husk, something like this happened:

Sample controller
template< typename T_IntegralType > class IntegralController { private: T_IntegralType *_modelField; QSpinBox *_view; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); /*     */ } return _view; } . . . private: // ,        void setValue(T_IntegralType inValue) { * modelField = inValue; } }; 



Having scribbled the code, I read about event handling in Qt, and realized that to work with events in general and with events from GUI elements in particular, I need to use a system of signals and slots (this system is well analyzed in this article - it seems this is a translation of the official docks) .

A note about the model-view approach
There is still such a thing as a delegate system within the model-view approach in Qt, which allows you to handle the transfer of data between the view and model through the implementation of interfaces, without a signal-slot system. For some reason, I was not able to properly use model-view Qt in my library.


In order for a class to provide slots for a signal-slot system, it was necessary that this class inherit from the QObject class and include the Q_OBJECT macro. What for? Then I did not steamed in order to understand. It is necessary - it means it is necessary. Without further ado, I added the required things to my template class and described the implementation of GUI event handling:

Template controller with event handling
 template< typename T_IntegralType > class IntegralController : public QObject { Q_OBJECT private: T_IntegralType *_modelField; QSpinBox *_view; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); QObject::connect(_view, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)); } } . . . private slots: void valueChanged(int inValue) { *_modelField = inValue; } }; 



All gathered. Great, I thought, what a fine fellow I am! - and continued to saw the library, for a time having forgotten about this template controller. Of course, in vain I hurried and generally rejoiced early, for he was not a good fellow. As soon as I tried to collect the code that uses the specialization of the template controller, the linker’s error messages fell down like this:

Link errors
 unresolved external symbol "public: virtual struct QMetaObject const * __cdecl TClass <int> :: metaObject (void) const" (? metaObject @? $ TClass @ H @@ UEBAPEBUQMetaObject @@ XZ)

 unresolved external symbol "public: virtual void * __cdecl TClass <int> :: qt_metacast (char const *)" (? qt_metacast @? $ TClass @ H @@ UEAAPEAXPEBD @ Z)

 unresolved external symbol "public: virtual int __cdecl TClass <int> :: qt_metacall (enum QMetaObject :: Call, int, void * *)" (? qt_metacall @? $ TClass @ H @@ UEAAHW4Call @ QMetaObject @@ HPEAPEAX @ Z)



It was obvious that I was doing something wrong with the meta-object system. I had to reread about MOC . The point was that MOC, when traversed by source, generates additional cpp-files in which the implementation of the methods necessary for the meta-object of the system is created. With templates, this system can work very crookedly, poorly generating this very meta-object code — the MOC either simply ignores the template classes when generating code, or treats them as normal classes, discarding template arguments, which for obvious reasons caused the above problems.

More about templates and Qt
There is even a separate article in the dock about why you should not use templates with the signal-slot system. Unfortunately, in my case it was not about optimality, but about a decent reduction in the amount of code and avoiding a large amount of save-paste - so the points from this article were not suitable for me.


Upon further acquaintance with MOC, it was discovered that there are still several restrictions on the use of classes under the control of MOC. The most unpleasant of them is the impossibility of describing fully-qualified MOC QObject heirs in nested classes. And I love to do nested classes, breaking the areas of responsibility of a large class between the smaller classics living inside it. Yes, I know, labor programmers use namespaces, but at the same time, in my opinion, the program context is cluttered up, and the semantics of nested classes differ from the semantics of namespaces (in the first case we build a hierarchy of relations between classes, in the second we simply group them by what - feature).

In general, given that I only needed the ability to subscribe to events through the static QObject :: connect (...) method, I decided to write a small crutch ... Just a small, small crutch.

Note: Kostylyk, as it turned out, turned out for the Qt version below the fifth, where the crooked MOC ruled. Qt 5 has a cool API that allows signal slots without this very MOC. Added this information at the end of the article. Again, the moral for me, a lazy author of articles, who is not at all well done: google seven times, write an article once .

About crutch



The idea was simple - to make a mini-class that would inherit a QObject and would be fully suitable for the MOC in the sense of registering a class for correct operation within the signal-slot system. This mini-class would also provide a method for associating a Qt-independent callback with a slot call in this auxiliary class.

Sounds complicated, the example will be clearer, I hope. In the event handling code from QSpinBox, it looked like this:

Crutchik for events from QSpinBox
 class ValueChangedWorkaround : public QObject { Q_OBJECT public: // -,  .     //  FastDelegate (     , ) typedef fastdelegate::FastDelegate1 < int > Callback; private: Callback _callback; public: ValueChangedWorkaround() : _callback() { } void bind(QSpinBox *inSpinBox, const Callback &inCallback) { _callback = inCallback; QObject::connect(inSpinBox, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)); } private slots: void valueChanged(int inValue) { _callback(inValue); } }; 


About FastDelegate in general
FastDelegate on github


I used this code in the controller - and it all worked:

Crutch Controller
 template< typename T_IntegralType > class IntegralController { private: typedef IntegralController< IntegralType > OwnType; T_IntegralType *_modelField; QSpinBox *_view; ValueChangedWorkaround _valueChangedWorkaround; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); _valueChangedWorkaround.bind(_view, ValueChangedWorkaround::Callback( &OwnType::valueChanged)); } } . . . private: void valueChanged(int inValue) { *_modelField = inValue; } }; 



It would seem that one could calm down on this ... But I am a pathological fan of universal solutions, so I decided to make a macro that allows you to create crutches for processing various events from Qt objects on an industrial scale.

Crutches lord

Crutches generator



It would seem that a new macro can be made on the basis of the old one by simply replacing some identifiers with macro arguments and slightly generalizing the macro itself.

Macro for generating crutches. Version 1.0
I omit here the slashes (here: "\") - terribly enraged!

 define QT_EVENT_WORKAROUND_1_ARG(M_WorkaroundName, M_EventName, M_Arg0Type) class M_WorkaroundName : public QObject { Q_OBJECT public: typedef fastdelegate::FastDelegate1 < M_Arg0Type > Callback; private: Callback _callback; public: M_WorkaroundName() : _callback() { } void bind(QObject *inQSignalSource, const Callback &inCallback) { _callback = inCallback; QObject::connect(inQSignalSource, SIGNAL(M_EventName(M_Arg0Type)), this, SLOT(M_EventName(M_Arg0Type)); } private slots: void M_EventName(M_Arg0Type inValue) { _callback(inValue); } }; 



Having written this macro, I thought with confidence that now I’m surely well done and generally a master of crutches (like the other man above). Very pleased, I ran the code and ... Yes, of course, nothing worked. There were no compilation errors, everything was going to happen, but the callback was not invoked, and there were messages in the log stating that, say, my TestWorkaround class does not have the correct slot.

I had to dig further. It turned out that MOC in Qt does not know how to deploy macros. It passes through the code before the preprocessor is executed (that is, not by the code that, for example, can be seen if you build with the -E flag in MinGW, but by no means processed code).
On the other hand, MOC should know the method signatures located in the blocks of the class declaration after the word “slots” - it reads them as strings and then uses these string names when calling QObject :: connect (SLOT and SIGNAL macros extract these names + some metadata about place of use). Thus, it became clear that in order to generate crutches, the user of the macro must be required to write his own implementation of the slot.

I tried to minimize the volume and complexity of this code and the final solution looks like this (already the final code, with godless slashes, aha):

 #define SIGNAL_WORKAROUND_1_ARG(M_WorkaroundName, M_CallName, M_Arg0Type)\ class M_WorkaroundName : public QObject {\ public:\ typedef fastdelegate::FastDelegate1< M_Arg0Type > Delegate;\ \ private:\ Delegate _delegate;\ \ public:\ void bind(QObject *inQSignalSource, const Delegate &inDelegate) {\ _delegate = inDelegate;\ QObject::connect(inQSignalSource, SIGNAL(M_CallName(M_Arg0Type)),\ this, SLOT(M_CallName(M_Arg0Type)));\ }\ \ void CALL(M_Arg0Type inArgument) { _delegate(inArgument); }\ 
- As you can see, the macro describes an incomplete class, which the user needs to end with a slot description for calling the CALL (...) method. Complete instructions for using the generator of crutches below ...

Full instructions:
1. Generate a crutch class somewhere using a macro. In the example, we will generate a crutch called YourWorkaroundName , which wraps the qtEventName event, which takes one argument of the type EventArg1Type ). Code for class generation:

 SIGNAL_WORKAROUND_1_ARG(YourWorkaroundName, qtEventName, EventArg1Type) Q_OBJECT private slots: void qtEventName(EventArg1Type a0) { CALL(a0); } }; 


2. Use a new type anywhere in the code where you need to handle events from any Qt objects that can send an event of a wrapped type (in the example, the qtEventName event, which sends a single argument of type EventArg1Type ). An example of code using a crutch:

 class UserClass { private: QSomeObject *_qTestObject; YourWorkaroundName _workaround; public: UsingClass() : _qTestObject(new QSomeObject()) { _workaround.bind(_qTestObject, YourWorkaroundName::Callback(&UsingClass:: onEvent)); } void onEvent(EventArg1Type inArg) { /* Some actions on callback */ } } 


All is ready. Now you can process messages from Qt objects in any classes, without restrictions imposed by Qt MOC.

In conclusion, a few comments:
1. The proposed macro is suitable for events that take one argument per input. To process a different number of arguments, you can either make a copy-paste of this macro (oldfag-style), or use the variadic macro from C ++ 11.
2. The proposed solution uses the FastDelegate library for working with callbacks. You can easily replace FastDelefate in a macro with your own type if you want to use your functors.
3. In this solution, there is no error handling, assertion, etc. - in my case, such processing is not required. You can add to taste.
4. I am ready to agree that the proposed solution is a hell of a disaster and I will be happy to hear suggestions how else you could cope with the limitations of Qt MOC. I will add your suggestions to the article indicating the authorship of the decision and with gratitude from myself personally. Thank you in advance!

Conclusion



I hope the proposed generator of crutches will help save someone the time to write a similar tin. Finally, I note that the generator of crutches contains a small amount of code, so, as for me, it makes no sense to post it on GitHub.
However, at the request of a respectable public, I can make a small turnip with a test drive. Express in the comments about, if there are more than five people willing, I’ll post.

Thank you for your attention and for reading!

PS: If you find any errors in the article - write, I will rule.


(Added 03/11/2016)

Thanks to AlexPublic and VioletGiraffe , they stuck their nose into the new signal-slot API .

If we talk about the generator of crutches, it will look like this:

Crutches generator
 #define SIGNAL_WORKAROUND_1ARG(M_WorkaroundName, M_CallName, M_Arg0Type)\ class M_WorkaroundName : public QObject {\ public:\ typedef fastdelegate::FastDelegate1< M_Arg0Type > Delegate;\ \ private:\ Delegate _delegate;\ \ public:\ template< typename T_ListenerType >\ void bind(T_ListenerType *inQSignalSource, const Delegate &inDelegate) {\ _delegate = inDelegate;\ connect(inQSignalSource,\ static_cast< void (T_ListenerType::*)(M_Arg0Type) > &T_ListenerType::M_CallName),\ this, &M_WorkaroundName::M_CallName);\ }\ \ void M_CallName(M_Arg0Type a0) { _delegate(a0); }\ }; 


Use made easier. Now you can create a wrapper type anywhere, including a template context. Something like this:

 template<typename T_SomeQObject> class UserClass { private: SIGNAL_WORKAROUND_1ARG(YourWorkaroundName, qtEventName, EventArg1Type); T_SomeQObject *_qTestObject; YourWorkaroundName _workaround; public: UsingClass() : _qTestObject(new T_SomeQObject()) { _workaround.bind(_qTestObject, YourWorkaroundName::Callback(&UsingClass::onEvent)); } void onEvent(EventArg1Type inArg) { /* Some actions on callback */ } } 



Although, it should be noted, this new signal-slot Qt5 API allows you to completely get rid of the MOC and its limitations. Therefore, instead of using a crutch, it is easier to inherit a class listener from a QObject (since there is no longer a binding to the MOC, the heir to the listener can be patterned and generally whatever), overlaying the QObject ifdef if you want to create a library with support for different GUI frameworks, or if you use C ++ 11 and older, and use lambda , as suggested by AlexPublic .

Source: https://habr.com/ru/post/279007/


All Articles