📜 ⬆️ ⬇️

Private slots in Qt's Pimpl pattern

Introduction.


I recently wrote about the implementation of the Pimpl pattern in the Qt library and encouraged people to follow this approach when developing their own bibiotech. Now I want to talk about such a concept as private slots and thus continue this topic. The final article on this topic will be the implementation of the mechanism of Implicit Sharing and the shared d-pointer.

What is it and why it is needed.


Private slots are a mechanism complementing the functionality of d-pointers. It allows you to implement slots for a private class, even if it is not a heir from QObject (usually it is not), but for this the public class must be a heir from QObject. Ie in fact, a certain private slot is created in a public class and it directly pulls the desired private class method.
Why do you need it? Well, consider an example. There is a class QAbstractScrollArea. It simply displays a certain widget (viewport) and provides scrolling. Scrolling is provided by using two instances of the QScrollBar class. He keeps these scrolbars in a private classroom. Now the problem is: how to connect the signal from the scrollbar about changing its position with the QAbstractScrollAreaPrivate class, because it is not a QObject? Make it a QObject successor - better not do it :-). You can make a slot in a public class and hang on it, then in this case it is not very beautiful - since slots from the internal implementation come out. Here that Qt-Schnick was thought up quite reasonable and elegant approach - private slots.


')

How it works.


To implement a private slot, use the Q_PRIVATE_SLOT macro. Well, according to our custom, we climb into the Qt source and see what it is like:

# define Q_PRIVATE_SLOT(d, signature)


Oops !!! Empty :-). But real men never despair and climb into the source more deeply, but rather into the source code moc. Who does not know, moc is a certain implementation of a certain parser. Just a little tricky. Unlike tools using QLALR (QSA and QXmlStreamReader) - this thing turned out to be unique. I am not silent in parsers so I cannot conduct a more global analysis. But he doesn’t exactly look like LALR because I haven’t found his vocabulary. The token table is generated by a certain generate_keywords utility, which can be found in the moc source in the util folder.
As I understand it, it simply generates tokens from any text and forms a structure of the form from them:
static const struct
{
Token token;
short next;
char defchar;
short defnext;
Token ident;
}


Here is an example of the records of this generator:
{Q_SIGNAL_TOKEN, 0, 83, 470, CHARACTER},
{Q_SIGNALS_TOKEN, 0, 0, 0, CHARACTER},
{Q_SLOT_TOKEN, 0, 83, 474, CHARACTER},
{Q_SLOTS_TOKEN, 0, 0, 0, CHARACTER},
{Q_PRIVATE_SLOT_TOKEN, 0, 0, 0, CHARACTER},


Who wants to skip further or even already knows the answer - please express.
By the way, this whole thing turned out to be very simple to expand :-) you can add your preprocessing. That's what it means to write beautifully.

in token.cpp we find our token:
case Q_PRIVATE_SLOT_TOKEN: return "Q_PRIVATE_SLOT_TOKEN" ;


And further in the implementation of the MOC :: parse () method we find what it does when it encounters this parser:
case Q_PRIVATE_SLOT_TOKEN:
parseSlotInPrivate(&def, access);
break ;



Well, that's got to the point, it is in the parseSlotInPrivate method (& def, access);
Here she is:
void Moc::parseSlotInPrivate(ClassDef *def, FunctionDef::Access access)
{
next(LPAREN);
FunctionDef funcDef;
next(IDENTIFIER);
funcDef.inPrivateClass = lexem();
// also allow void functions
if (test(LPAREN)) {
next(RPAREN);
funcDef.inPrivateClass += "()" ;
}
next(COMMA);
funcDef.access = access;
parseFunction(&funcDef, true );
def->slotList += funcDef;
while (funcDef.arguments.size() > 0 && funcDef.arguments.last().isDefault) {
funcDef.wasCloned = true ;
funcDef.arguments.removeLast();
def->slotList += funcDef;
}
}


* This source code was highlighted with Source Code Highlighter .


Now everything is clear. As you can see, this directive fills in the necessary values ​​to define the meta-function that is generated by the moc (in the same way as for regular slots). And the empty define simply makes this entry empty when the moc has passed and the time comes for the precompiler. Ie at the time of standard compilation there is already empty, but the implementation of the slot is generated in the moc file. Next, I will show how it looks in our example.
Well, in general, figured out how it works, will continue to look at how to use it.

How to implement this in your code.


Here in reality everything is very simple. As an example, take the classes from my previous article . And we add new functionality.
1. Add the void _q_boo () method to MyClassPrivare:
class MyClassPrivate
{
Q_DECLARE_PUBLIC(MyClass);
public :
MyClassPrivate();
virtual ~MyClassPrivate();

void foo();
void _q_boo();
int i;
MyClass * q_ptr;
};


* This source code was highlighted with Source Code Highlighter .


The implementation in myclass.cpp will look like this:
void MyClassPrivate::_q_boo()
{
qDebug()<<i;
QCoreApplication::exit(1);
}


Do not forget to add QDebug and QCoreApplication to the included.
About the name _q_boo. This is the nt private slot naming rule. The name must begin with "_q_". Then, when viewing a private class ad, you can determine that it is a slot.
QCoreApplication :: exit (1) - I added so that when we finish, we complete the main application cycle (I’ll write a listing of this application to test our library).
2. For more entanglement (and then everything turns out to be very simple), let us jerk this slot into the heir - MyClassDerived. To do this, we declare the following macro in his private section:
Q_PRIVATE_SLOT(d_func(), void _q_boo());


From the above reverse engineering moc we see here that we need a pointer to the class and the name of its method. In fact, this is a simple text analysis based on which then the code moc_myclassderived.cpp is generated.
3. Let's try to assemble our project:
moc_myclassderived.cpp: In member function ' virtual int MyClassDerived::qt_metacall(QMetaObject::Call, int , void **)':
moc_myclassderived.cpp:70: error: invalid use of incomplete type ' struct MyClassDerivedPrivate'
myclassderived.h:5: error: forward declaration of ' struct MyClassDerivedPrivate'


This is normal, because moc knows nothing about our Private class, because it only includes the header file, on the basis of which it was generated. And in the public header file only forward announcement. Since moc is essentially an addition to our implementation, simply include it at the end of our .cpp file:
..................
#include "moc_myclassderived.cpp"



Now we start the assembly and ... Again the same error !!! Do not despair. Just do a make distclean and build again.
Well, in general, and all ... ahhh of course, an example of use. Then we go further.
As with the public, we’re looking at what the nocheneril moc gives us:
int MyClassDerived::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = MyClass::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: signal2((*reinterpret_cast< int (*)>(_a[1]))); break ;
case 1: d_func()->_q_boo(); break ;
default : ;
}
_id -= 2;
}
return _id;
}


Please note: when a request to call a slot with _id = 1 comes to us, we pull the private class method directly, without any loss, as if this metacall system is native to the private class and not to the public (I’m a little wrong, because there is a small overhead in type conversion during the d_func () function call.

4. Let's start the timer so that it yanked our method when creating the class MyClassDerived in one second.
First we declare the init () method in MyClassDerivedPrivate.
The implementation in myclassderived.cpp looks like this:
void MyClassDerivedPrivate::init()
{
Q_Q(MyClassDerived);
QTimer::singleShot(1000,q,SLOT(_q_boo()));
}



Well, let's add an initialization call to our constructor (we used to have them empty):
MyClassDerived::MyClassDerived(QObject *parent)
:MyClass(* new MyClassDerivedPrivate(), parent)
{
Q_D(MyClassDerived);
d->init();

}

MyClassDerived::MyClassDerived(MyClassDerivedPrivate &dd, QObject * parent)
:MyClass(dd, parent)
{
Q_D(MyClassDerived);
d->init();
}


Of course, in this case, it was possible not to fence the construction with init (), it would just be like in each constructor:
QTimer::singleShot(1000, this ,SLOT(_q_boo()));

But I wanted to show the approach when more than one action is needed in the initialization.

Do not believe it :-) But that's all. In the next paragraph I will show the program that will test all this.

We are testing our code


Well, we are creating a new project (in my case, I made it in the main subdirectory of my project habrahabra):
main.pro
TEMPLATE = app
LIBS += -lhabrhabr -L..
INCLUDEPATH += ../
TARGET = main

SOURCES += main.cpp


* This source code was highlighted with Source Code Highlighter .


well, main.cpp itself:
#include "myclassderived.h"
#include <QApplication>

int main( int argc, char ** argv) {
QApplication a(argc,argv);
MyClassDerived d(0);
return a.exec();
}


* This source code was highlighted with Source Code Highlighter .


Why was it necessary to do QApplication? Yes, because the system of signals and slots will not work without a thread execution loop in the context of which it will be executed (in our case, the mainloop).
We collect, run, we get the result:
7
exit-code:1



Uraaa !!! Everything is working.

Conclusion


Maybe it scared you that I wrote so much on this topic? In this case, I can reassure you, everything is much easier than you think. I just wanted to show how this mechanism works, and not just instructions for use, like: "Insert this here and this here and hurray it all works for you." In fact, you need to do just three steps.
1. Declare the Q_PRIVATE_SLOT (d_func (), _ q_method ()) macro;
2. Implement this method in a private class.
3. Add to the end of the cpp file #include "moc_classname.cpp"

And that's it !!! It's simple.

Important: I already specify the order of the header files in the article regarding pimpl. But here I will remind you that all private header files must strictly follow the public ones and they must go in the order of inclusion in the HEADERS list. In our example, the derived class includes the base class headers, so in the HEADERS list of the project file it comes after the parent headers.
Well, just like d-pointer macros, private slot macros are not part of the public API and can be changed at any time. But do not worry, I reassured about this in the article on pimpl.

As always, I invite you to visit my blog: erudenko.com . There you can read the bourgeois version of this article and various additions. You can write there komenty, I will answer as much as possible.

Well, according to tradition, the source code (the source code of the test program is fully published above, so here I will not write them):
habrhabr.pro:
TEMPLATE = lib
HEADERS += myclass.h \
myclass_p.h \
myclassderived.h \
myclassderived_p.h
SOURCES += myclass.cpp \
myclassderived.cpp


* This source code was highlighted with Source Code Highlighter .


myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

class MyClassPrivate;
class MyClass : public QObject
{
Q_OBJECT
public :
explicit MyClass(QObject *parent = 0);
int foo() const ;
signals:
void signal( int );
protected :
MyClassPrivate * const d_ptr;
MyClass(MyClassPrivate &dd, QObject * parent);
private :
Q_DECLARE_PRIVATE(MyClass);
};

#endif // MYCLASS_H


* This source code was highlighted with Source Code Highlighter .


myclass_p.h
#ifndef MYCLASS_P_H
#define MYCLASS_P_H
#include "myclass.h"

class MyClassPrivate
{
Q_DECLARE_PUBLIC(MyClass);
public :
MyClassPrivate();
virtual ~MyClassPrivate();

void foo();

void _q_boo();

int i;
MyClass * q_ptr;
};

#endif // MYCLASS_P_H


* This source code was highlighted with Source Code Highlighter .


myclass.cpp
#include "myclass.h"
#include "myclass_p.h"
#include <QDebug>;
#include <QCoreApplication>;
MyClassPrivate::MyClassPrivate()
{
i = 5;
}

MyClassPrivate::~MyClassPrivate()
{
//nothing to do
}

void MyClassPrivate::foo()
{
Q_Q(MyClass);
emit(q->signal(i));
}

void MyClassPrivate::_q_boo()
{
qDebug()<<i;
QCoreApplication::exit(1);
}

MyClass::MyClass(QObject *parent)
:QObject(parent)
,d_ptr( new MyClassPrivate())
{
Q_D(MyClass);
d->q_ptr = this ;
}

MyClass::MyClass(MyClassPrivate &dd, QObject * parent)
:QObject(parent)
,d_ptr(&dd)
{
Q_D(MyClass);
d->q_ptr = this ;
}

int MyClass::foo() const
{
Q_D( const MyClass);
return d->i;
}


* This source code was highlighted with Source Code Highlighter .


myclassderived.h
#ifndef MYCLASSDERIVED_H
#define MYCLASSDERIVED_H
#include "myclass.h"

class MyClassDerivedPrivate;
class MyClassDerived : public MyClass
{
Q_OBJECT
public :
explicit MyClassDerived(QObject *parent = 0);
signals:
void signal2( int );
protected :
MyClassDerived(MyClassDerivedPrivate &dd, QObject * parent);
private :
Q_DECLARE_PRIVATE(MyClassDerived);
Q_PRIVATE_SLOT(d_func(), void _q_boo());
};

#endif // MYCLASSDERIVED_H


* This source code was highlighted with Source Code Highlighter .


myclassderived_p.h
#ifndef MYCLASSDERIVED_P_H
#define MYCLASSDERIVED_P_H

#include "myclassderived.h"
#include "myclass_p.h"

class MyClassDerivedPrivate: public MyClassPrivate
{
Q_DECLARE_PUBLIC(MyClassDerived);
public :
MyClassDerivedPrivate();
virtual ~MyClassDerivedPrivate();

void foo2();
void init();
int j;
};

#endif // MYCLASSDERIVED_P_H


* This source code was highlighted with Source Code Highlighter .


myclassderived.cpp
#include "myclassderived.h"
#include "myclassderived_p.h"
#include <QTimer>;

MyClassDerivedPrivate::MyClassDerivedPrivate()
{
j=6;
i=7;
}

MyClassDerivedPrivate::~MyClassDerivedPrivate()
{

}

void MyClassDerivedPrivate::foo2()
{
Q_Q(MyClassDerived);
emit(q->signal2(j));
emit(q->signal(j));
}

void MyClassDerivedPrivate::init()
{
Q_Q(MyClassDerived);
QTimer::singleShot(1000,q,SLOT(_q_boo()));
}

MyClassDerived::MyClassDerived(QObject *parent)
:MyClass(* new MyClassDerivedPrivate(), parent)
{
Q_D(MyClassDerived);
d->init();

}

MyClassDerived::MyClassDerived(MyClassDerivedPrivate &dd, QObject * parent)
:MyClass(dd, parent)
{
Q_D(MyClassDerived);
d->init();
}

#include "moc_myclassderived.cpp"


* This source code was highlighted with Source Code Highlighter .

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


All Articles