⬆️ ⬇️

What is a Pimpl version of Qt, and with what it is eaten!

Introduction.





The term Pimpl is often used in Qt documentation. In addition, those who dug a little bit in Qt source code often saw such macros as: Q_DECLARE_PRIVATE, Q_D. And also met the so-called private header files, whose name ends with "_p.h".

In this article I will try to open the screen behind all this structure.



Pimpl, what is it?



Pimpl - Pointer to private implementation. This is one of the names of the programming pattern. He is also called the Cheshire cat - “Cheshire Cat” (I like this name more). What is the essence of this pattern? The main idea of ​​this pattern is to remove all private members of a class and, in some cases, the functionality to a private class.

Hence the name "Cheshire Cat" - you can only see a smile, and everything else remains invisible, but it certainly is :-) Who does not remember this wonderful cat, may turn to the original source, to the book by Lewis Carroll "Alice in Wonderland". Very interesting book, especially if read in the original.

What does this give?



  1. If the class hierarchy “wide” or “deep” turns out to be a more complex class structure and thereby increases the convenience of “reuse - reuse” code.
  2. It also makes it possible to hide the platform-dependent implementation from the end user.
  3. One of the main purposes of this pattern is to provide a mechanism for implementing the binary compatibility of a library when its implementation changes (achieved due to the fact that the entire implementation is in a private class). More details about binary compatibility and binary application interface (ABI) can be found here (Itanium C ++ ABI) and here (an article about calling conversion) . And the basic rules about binary compatibility from KDE developers are Binary Compatibility Issues With C ++ with a brief collection of what you need and cannot do to preserve binary compatibility.
  4. The next advantage is that the number of exported characters in the class becomes smaller and the library loading speed increases, well, plus a smaller size of course.
  5. Increases the application build speed (which is very important).
  6. All unnecessary implementation is hidden from the client, in contrast to private methods, pimpl declaration and implementation are not visible at all.
  7. This pattern greatly facilitates the implementation of the implicit-sharing mechanism (I will not translate, so as not to produce unnecessary terminology). The mechanism by which when copying classes does not copy data, and copying occurs only when a copy of the class will need to change this data. Implicit-sharing is implemented in all Qt container classes. To implement it, a Pimpl implementation called “shared D-pointers” is used. In general, this is a capacious topic and requires a separate article.


')

But there must be cons, I will not hide them, I’ll post it as it is:

  1. Each constructor, destructor cause the allocation and release of memory, which increase the time to create a class.
  2. Each method that contains access to the private implementation adds plus one extra instruction.




Given these shortcomings, according to the author, the use of this pattern for classes that will be contained in the code in large numbers and created / deleted in the course of the life of the program is impractical. Suppose an example of such an implementation could be loading and storing some data; here are the classes that implement this data and make it as easy as possible. And any change in such classes assumes that the protocol of the transfer of this data changes. But there may be other examples of such classes. Therefore, the basic rule when using any pattern also applies to this one: it is necessary to use it optimally, where it is really necessary.

That is, this pattern should be used in the event that you are writing a library or are planning to bring this functionality into a separate library in the future. In other cases, in the personal opinion of the author, the use of such an approach is excessive.



How this is implemented in the Qt libraries.



In Qt code, the d-pointer approach is used. The point is that the class XXXPrivate and the public class variable in the protected section are declared. The implementation of a private class is already written in a separate header file or in a .cpp file (I will explain the difference later).

The class hierarchy goes both in public and in private classes. For this, the declaration of a private class is usually done in a separate .h file, which is also called public, but the prefix _p is added: qclassname_p.h. And these classes are not installed along with the library, but serve only to build the library. Therefore, you will not find them in the path where the QT libraries (Prefix-PATH) are installed.



What are the advantages of the d-pointers approach from Qt?



At first glance, this approach may seem a bit confusing, but I assure you that it is in fact very simple and intuitive, and even facilitates readability of the code (I refer to this as a subjective property, therefore it is debatable, everything depends on the perception of a particular person).

Advantages:

  1. Simple and visual.
  2. Direct access to the entire hierarchy of private classes (in fact, they are not private :-), but protected).
  3. The ability to access public class from private.
  4. The ability to implement a system of signals and slots for a private class and hide them from external use with the help of the Q_PRIVATE_SLOT macro (topic for a separate article).


I want the same, but with pearl buttons! (c) C / f "Diamond Arm"



If I convinced you that Pimpl is good, and you want to try and see how it works, then let me dedicate you to the implementation of Pimpl according to Qt.

What should be done:

1. Make forward - declare your private class before declaring a public class:

class MyClassPrivate;

class MyClass: public QObject

{ ..............




2. Further, in the first class of the hierarchy in the protected section, you must declare a variable that refers to a private class:

protected :

MyClassPrivate * const d_ptr;




Note that the pointer is constant, in order to avoid all sorts of random absurdities there.

3. Also in the protected section (as the first class of the hierarchy and all its heirs) it is necessary to declare a constructor that takes a private member of the class as a parameter. This is necessary in order to ensure the possibility of creating heirs of a private class and using them as private classes in the entire hierarchy:

protected :

MyClass(MyClassPrivate &&d, QObject *parent);




4. In the private section, we declare a d-pointer access macro:

Q_DECLARE_PRIVATE(MyClass);



Now we understand what he is:

#define Q_DECLARE_PRIVATE(Class) \

inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \

inline const Class##Private* d_func() const { return reinterpret_cast< const Class##Private *>(d_ptr); } \

friend class Class##Private;




As we see here, the function d_func () and d_func () const are declared, with the help of which we get a pointer to a private class and a constant private class, respectively. And we get it already reduced to the type of the private class of this object. Well, we declare our private class a friend to the public.

There is also a Q_DECLARE_PRIVATE_D macro. The difference is that you specify a d-pointer variable as the second parameter, so instead of d_ptr, a variable with any name can be used as a D-pointer. But the name of the function d_func remains unchanged.

The macro implementation looks like this:

#define Q_DECLARE_PRIVATE_D(Dptr, Class) \

inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(Dptr); } \

inline const Class##Private* d_func() const { return reinterpret_cast< const Class##Private *>(Dptr); } \

friend class Class##Private;




5. Now you need to declare our private class in the .cpp or _p.h file. If you intend to further inherit from your class or are going to use private slots, then you need to put everything into a separate _p.h file. If not, then it is enough to declare a private file in a .cpp file. Also keep in mind that in the .pro file, the .h file must go to the _p.h file and to all the files that will include it. This can be generally taken as a rule, as it makes the work easier for the compiler.

class MyClassPrivate

{

MyClassPrivate();

virtual ~MyClassPrivate();



int i;

}




I also recommend making the destructor virtual if you plan to build a hierarchy of private classes. Why? This is a topic for a separate article and there are already enough such articles written, and of course if you don’t believe or trust the Internet, then refer to Straustrup, this topic is described in detail.

7. The implementation of the constructor from the protected section will look something like this:

MyClass::MyClass(MyClassPrivate &dd, QObject* parent)

:QObject(parent)

,d_ptr(&dd)

{ .....




Well, the usual constructor with this declaration (pay attention to the keyword explicit, if you do not know what it is and why, ask - this is useful):

explicit MyClass(QObject * parent);



It will look like this:

MyClass::MyClass(QObject * parent)

:QObject(parent)

,d_ptr( new MyClassPrivate())

{........




In the heir, the implementation of such a constructor will look like this:

MyClassDerived::MyClassDerived(QObject * parent)

:MyClass(* new MyClassDerivedPrivate(),parent)

{........




As you can see, the corresponding constructor of the inheritor passes an instance of its private class to all base classes along the inheritance hierarchy chain (the class hierarchy is the same in Qt; the very first class in the private class hierarchy is QObjectData, which contains the parent, object state and other basic properties).

8. To access an instance of a private class from a public class method, there is a Q_D () macro. This is what it is:

#define Q_D (Class) Class ## Private * const d = d_func ()

As you can see, we get a constant pointer to our private class in the form of a variable “d” (here it is a D-pointer: -) !!! ).

int MyClass::foo() const

{

Q_D( const MyClass);

return d->i;

}




Please note that in constant methods you need to write const before the class name in a Q_D macro to get a constant pointer to a constant instance of a private class (if this wording scares you or is not completely clear, refer to the documentation on "const", believe me - this is very important ).

9. Now dive deeper. Let me introduce another beast ;-): Q-pointer. A Q-pointer (aka Q-pointer) is the same D-pointer (aka D-pointer), only with the exact opposite. It serves for access from the methods of a private class to a public instance (usually used in those cases if the logic is also moved to a private class, or it is planned to do this further along the chain of hierarchy).

To implement it, in the very first class of the private hierarchy it is necessary to declare a pointer variable to the base class:

class MyClassPrivate

{

public :

MyClassPrivate();

virtual ~MyClassPrivate();



int i;

MyClass *q_ptr;

}




And in all hierarchy classes, declare the macro Q_DECLARE_PUBLIC, in which you plan to use a Q-pointer.

class MyClassPrivate

{

Q_DECLARE_PUBLIC(MyClass);

public :

MyClassPrivate();

virtual ~MyClassPrivate();



int i;

MyClass *q_ptr;

}




This is what the Q_DECLARE_PUBLIC macro is:

#define Q_DECLARE_PUBLIC(Class) \

inline Class* q_func() { return static_cast<Class *>(q_ptr); } \

inline const Class* q_func() const { return static_cast< const Class *>(q_ptr); } \

friend class Class;




As you can see, everything is the same, as in Q_DECLARE_PRIVATE, except for the names. Well, there is no macro for the alternative name q_ptr, like Q_DECLARE_PRIVATE_D.

Important: do not forget to take care in all the constructors of the first class of the public hierarchy about the initialization of the variable q_ptr:

MyClass::MyClass(QObject * parent)

:QObject(parent)

,d_ptr( new MyClassPrivate())

{

Q_D(MyClass);

d->q_ptr = this ;

.......

}






10. To access from a private class method to a public class (for example, to make a call to a public class signal), there is a Q_Q macro, here’s how it looks:

#define Q_Q(Class) Class * const q = q_func()





The logic is the same as for D-pointers, and the same rules. Well, in the code it will look like this:



void MyClassPrivate::foo()

{

Q_Q(MyClass);

q->foo();

emit(q->signal(i));

}






Conclusion





Note that all of these macros are not part of the public API, and can be changed at any time. But I can calm you down. Firstly, all this is such a basic foundation that at least this will change to the new major version, but in this case you will still have to port the application. And secondly, many large projects use these macros: for example, KDE. Well, if you are a convinced paranoid and do not trust anyone, you can declare similar macros in your global file, change their name and use them in the code, then you’re definitely afraid of nothing (except for changing the behavior of compilers with respect to macros :-), because paranoid there is always something to fear :-)).

Also keep in mind that in my example, I inherit from QObject, which uses the same macros to build its hierarchy of both public and private classes. But my private class hierarchy has nothing to do with the Qt private class hierarchy. They stand aside and do not interfere with each other. Since I have overlapped the d_ptr variable in my class, and for all heirs from my class, d_ptr will be a pointer to my hierarchy, but not for QObject. For it, d_ptr will be a Qt hierarchy of private classes (more precisely, a pointer to a QObjectPrivate).

A reasonable question may arise: why not inherit our private class from QObjectPrivate. Answer: it is possible, but first of all, binary compatibility with the Qt library will be lost (we will need to have our library for each version of the private implementation in Qt (it also changes, QWidget is accurate)). And the second argument against is that you will need private for building our library Qt library header files that are located in the src folder of the sources, and not include the installed Qt libraries. And it's hard to imagine why this is necessary (maybe someone will present counter-arguments, I will be glad).



In the future, I will tell you some more interesting things:

What is QFlag, what is its advantage and what to eat with it.

Code execution rules in Qt-style.

How to implement Implicit Sharing and what is Shared D-pointer.

Useful macros in QT.

Qt Creator inside and out.

How to write an application for 7 platforms with minimal effort.

And many other interesting things.



PS: I am going to post new and interesting articles on my erudenko.com site , though in bourgeois language. If possible, I will translate it into Russian and post it here.



PPS: I apologize for my Russian language, possible incorrect speech circulation and errors (both spelling and syntax) are not my native language :-)



Fuuuuuuh, well, that's all. As a supplement, a simple example of implementation, otherwise I wrote a lot of letters, but with an example it is always clearer:



.pro file:

  1. TEMPLATE = lib
  2. HEADERS + = myclass.h \
  3. myclass_p.h \
  4. myclassderived.h \
  5. myclassderived_p.h
  6. SOURCES + = myclass.cpp \
  7. myclassderived.cpp




myclass.h file:



  1. #ifndef MYCLASS_H
  2. #define MYCLASS_H
  3. #include <QObject>
  4. class MyClassPrivate;
  5. class MyClass: public QObject
  6. {
  7. Q_OBJECT
  8. public :
  9. explicit MyClass (QObject * parent = 0);
  10. int foo () const ;
  11. signals:
  12. void signal ( int );
  13. protected :
  14. MyClassPrivate * const d_ptr;
  15. MyClass (MyClassPrivate & dd, QObject * parent);
  16. private :
  17. Q_DECLARE_PRIVATE (MyClass);
  18. };
  19. #endif // MYCLASS_H




file myclass_p.h

  1. #ifndef MYCLASS_P_H
  2. #define MYCLASS_P_H
  3. #include "myclass.h"
  4. class MyClassPrivate
  5. {
  6. Q_DECLARE_PUBLIC (MyClass);
  7. public :
  8. MyClassPrivate ();
  9. virtual ~ MyClassPrivate ();
  10. void foo ();
  11. int i;
  12. MyClass * q_ptr;
  13. };
  14. #endif // MYCLASS_P_H




myclass.cpp file

  1. #include "myclass.h"
  2. #include "myclass_p.h"
  3. MyClassPrivate :: MyClassPrivate ()
  4. {
  5. i = 5;
  6. }
  7. MyClassPrivate :: ~ MyClassPrivate ()
  8. {
  9. // nothing to do
  10. }
  11. void MyClassPrivate :: foo ()
  12. {
  13. Q_Q (MyClass);
  14. emit (q-> signal (i));
  15. }
  16. MyClass :: MyClass (QObject * parent)
  17. : QObject (parent)
  18. , d_ptr ( new MyClassPrivate ())
  19. {
  20. Q_D (MyClass);
  21. d-> q_ptr = this ;
  22. }
  23. MyClass :: MyClass (MyClassPrivate & dd, QObject * parent)
  24. : QObject (parent)
  25. , d_ptr (& dd)
  26. {
  27. Q_D (MyClass);
  28. d-> q_ptr = this ;
  29. }
  30. int MyClass :: foo () const
  31. {
  32. Q_D ( const MyClass);
  33. return d-> i;
  34. }




file myclassderived.h

  1. #ifndef MYCLASSDERIVED_H
  2. #define MYCLASSDERIVED_H
  3. #include "myclass.h"
  4. class MyClassDerivedPrivate;
  5. class MyClassDerived: public MyClass
  6. {
  7. Q_OBJECT
  8. public :
  9. explicit MyClassDerived (QObject * parent = 0);
  10. signals:
  11. void signal2 ( int );
  12. protected :
  13. MyClassDerived (MyClassDerivedPrivate & dd, QObject * parent);
  14. private :
  15. Q_DECLARE_PRIVATE (MyClassDerived);
  16. };
  17. #endif // MYCLASSDERIVED_H




file myclassderived_p.h

  1. #ifndef MYCLASSDERIVED_P_H
  2. #define MYCLASSDERIVED_P_H
  3. #include "myclassderived.h"
  4. #include "myclass_p.h"
  5. class MyClassDerivedPrivate: public MyClassPrivate
  6. {
  7. Q_DECLARE_PUBLIC (MyClassDerived);
  8. public :
  9. MyClassDerivedPrivate ();
  10. virtual ~ MyClassDerivedPrivate ();
  11. void foo2 ();
  12. int j;
  13. };
  14. #endif // MYCLASSDERIVED_P_H




myclassderived.cpp file

  1. #include "myclassderived.h"
  2. #include "myclassderived_p.h"
  3. MyClassDerivedPrivate :: MyClassDerivedPrivate ()
  4. {
  5. j = 6;
  6. i = 7;
  7. }
  8. MyClassDerivedPrivate :: ~ MyClassDerivedPrivate ()
  9. {
  10. }
  11. void MyClassDerivedPrivate :: foo2 ()
  12. {
  13. Q_Q (MyClassDerived);
  14. emit (q-> signal2 (j));
  15. emit (q-> signal (j));
  16. }
  17. MyClassDerived :: MyClassDerived (QObject * parent)
  18. : MyClass (* new MyClassDerivedPrivate (), parent)
  19. {
  20. // Empty
  21. }
  22. MyClassDerived :: MyClassDerived (MyClassDerivedPrivate & dd, QObject * parent)
  23. : MyClass (dd, parent)
  24. {
  25. // Empty
  26. }

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



All Articles