Skip to content

November 27, 2009

Private slot implementation in pimpl pattern by Qt-way.

Introduction.

Some time before I wrote about pimp pattern by Qt-way. In that post I was trying to show you the good way of using pimpl pattern, and use it at all. Especially when you implement your own library. Binary compatibility is good idea and d-pointers help to make it easily. Now I’ll try to extend you knowledges about this pattern, and show you new useful tool for making d-pointers better and more flexible. The next post is finally will close the pimpl topic: it will be about shared d-pointers and implicit sharing. But this topic is about private slots, the mechanism, that common used by Qt classes.

What is it? And why to use it?

Private slot is extension of d-pointers. Private slots give you way to realize slots in private class, even if private class is not QObject ( it’s not QObject almast in all classes ). But public class have to be QObject. Really the slot is not Private’s class slot, it is part of public class.
Why we need slot in private class? Let’s look at some example. There is exists QAbstractScrollArea class.It’s simply displays it’s view
port widget, and move it, if it has large geometry. QAbstractScrollAreaPrivate has two QScrollBars to make opportunity to user move viewport’s widget. Ok, and what to do when user changed scrollbar’s value ? In common way we need to connect appropriate signal of scrollbar to slot of our class, that implement necessary behaviour (scroll in our case). But this slots will became available outside our class in this case. It’s not good idea to show private implementation outside. The other possible solution is to make private class derived from QObject. Don’t do this (you have very heavy reasons to do it, and private slots is not one of them)!!!
Don’t worry, Trolls made a grade solution – private slots! This is very simple and flexible way to resolve this issue.

How does it work?

To implement private slot there is exists Q_PRIVATE_SLO macros. As usual, let’s look inside this macro:

# define Q_PRIVATE_SLOT(d, signature)

Wow!!! it’ empty. Why? Where is our macros’s definition? Hmmmm. The real man doesn’t afraid to come up against an obstacle. That’s why let’s look inside Qt’s moc preprocessor’s source code. The internal model of moc is looks like any lexer parser. But It’s not look like it made by QLALR, yacc or something else. I know that Trolls used QLALR ( their internal software ) to generate parsers – Qt Script Engine, QXMLStreamReader and maybe something else. But not moc. I’m not very strong with parsers, and it’s not our target ( at this moment :-) ), that’s why I’ll only notice you about our research on Q_PRIVATE_SLOT macros.
Tokens are generated by generate_keywords. You can find it in util subfolder of moc’s source folder.
The output of this tool is array of such structures:

static const struct
{
   Token token;
   short next;
   char defchar;
   short defnext;
   Token ident;
}

Here are records about slots, signals and our private signal:

    {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},

Now we know that tokenizer generates token Q_PRIVATE_SLOT_TOKEN for our macros during parsing of code.

here it is in token.cpp:

        case Q_PRIVATE_SLOT_TOKEN: return "Q_PRIVATE_SLOT_TOKEN";

End then let’s find logic of parser. What parser has doing when met this token. This implementation is in MOC::parse() method:

              case Q_PRIVATE_SLOT_TOKEN:
                    parseSlotInPrivate(&def, access);
                    break;

Yes! We have found it!
the parseSlotInPrivate method is what we have been looking for. Let’s look at definition:

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

It’s all clear and rather simple. As you can see, this function is fills funcDef class/struct with appropriate values and adds this funvDef to regular slot List, like for normal slot does. And after moc did it, preprocessor replaced this macro with empty string regarding macros definition. And when compile build it, there is nothing left. But the slot’s meta definition has been realized in moc_.cpp file.
Very nice approach. Also you have opportunity to extend moc’s preprocessing functionality by hacking it :-)
Ok. Now we know how it works, let’s look how to use it.

How to use it in your code?

It’s totally simple. Let’s extend my previous example.
1. Let’s make new method void _q_boo() in MyClassPrivare class:

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

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

definition is in myclass.cpp looks like:

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

Don’t forget to include QDebug and QCoreApplication headers.
Some words about strange name _q_boo. It’s Qt’s naming rule for private slots. Name have to start with “_q_”. In this case it’s easy to recognize it in class’s declaration and definition.
I added QCoreApplication::exit(1) to exit application when slot will execute. But keep in mind that is not a good decision at all. It’s only for testing and demonstrationg of our library.
2. To make it more realistic ( in other case it’s too simple ) let’s invoke this slot from derived class.We have to declare macros in classes declaration:

	Q_PRIVATE_SLOT(d_func(),void _q_boo());

Regarding previous research about moc, we can explain that we need two arguments: first – target object, second – target method.

3. Let’s try to build 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’

Don’t be afraid. That all ok. Moc doesn’t know anything about our private class, that’s why when it generates the moc file it only generate code regarding parsed .h file without checking cpp model at all. And it includes only header, that was target of moc generation process. myclassderived.h in our case. And there is only forward declaration in private header. But we unable to say to moc compiler to add extra header to include section.
The generated moc file “moc_myclassderived.cpp” is simply extension to our myclassderived.cpp file. That’s why it’s save to add it at end of .cpp file. From this place the parivate class is accessible, because we have included it’s header at the top of .cpp file.

..................
#include "moc_myclassderived.cpp"

Let’s build project one more. O-o-o-u. The same error :-( . Don’t be despairing of success ! Simply run command “make distclean” and build it again.
That’s all. O, no! I forgot about example. Ok, let’s go next.
First let’s look on moc’s generated code for our slot (as I promised before).

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

Keep attention, when method receives query to invoke slot with _id=1, it simply invoke method _q_boo() from our private class directly. Without any additional steps and callings, like it is native Private Class’s meta call system (It’s not totally true, because there is little overhead, when calling d_func(), because it casts d_ptr to our Private Class’s type).

4. Let’s start timer in constructor, which will emit signal and execute our slot after 1 second.
First of all we need add init() method in private class.
Definition in myclassderived.cpp is look’s like this:

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

And now let’s add initialization of private class in constructors of public one:

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 it’s better to add only one line in each constructor:

    QTimer::singleShot(1000,this,SLOT(_q_boo()));

But I tried to show you concept at all, and there are usually more than one line of code in initialization method.
Believe it or not, that’s all. In the next section I’ll show you test application for our library.

Testing our library.

Lets’ make new project (subfolder “main” in my case).
main.pro

TEMPLATE = app
LIBS += -lhabrhabr -L..
INCLUDEPATH += ../
TARGET = main

SOURCES += main.cpp

and the main.cpp:

#include "myclassderived.h"
#include <QApplication>

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

Why I made QApplication’s instance. Because signals and slots are working in context of thread loop, and we need this loop for our code. In our case we use main loop of application.
Build, Run, and having results:

7
exit-code:1

Ta-a-a-T-a-a-m! We made it!

Warning: I noticed about headers order in .pro file before. Here I’m repeating about it. Headers of private class HAVE TO BE AFTER THEIR PUBLIC headers in HEADERS section of .pro file. Also headers have to be in order they include each other. For example, myclass.h have to be before myclassderived.h, because myclassderived.h include’s it.
Keep in mind, that Q_PRIVATE_SLOT is not a part of public API, and Trolls are not make any guaranties, that it will have the same behavior in next releases. But don’t be afraid, I noticed in the post about pimpl, that it’s a base of all Qt’s realization, that’s why It might be changed only at next major releases, for example in Qt5.

And now source code, as usual (source code of test program is in previous section):
habrhabr.pro:

TEMPLATE = lib
HEADERS += myclass.h \
    myclass_p.h \
	myclassderived.h \
    myclassderived_p.h
SOURCES += myclass.cpp \
    myclassderived.cpp

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

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

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

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

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

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"

Unique visitors to post: 132

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments