What is Qt's Pimpl, and what is it for!

    Introduction.



    Often in the documentation from Qt, the term Pimpl is used. In addition, those who at least delved into the Qt source code often saw macros such as: Q_DECLARE_PRIVATE, Q_D. And also met the so-called private header files, the name of which 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 a Cheshire cat - "Cheshire Cat" (I like this name more). What is the essence of this pattern? The main idea of ​​this pattern is to move all private members of a class and, in some cases, functionality to a private class.
    Hence the name “Cheshire cat” - only a smile is visible, and everything else remains invisible, but it certainly is :-) Whoever does not remember this wonderful cat can turn to the original source, to Lewis Carroll’s book “Alice in Wonderland”. A very interesting book, especially if you read in the original.
    What does it give?

    1. If the class hierarchy is “wide” or “deep”, a more complex class structure is obtained, and thereby the convenience of “reuse - reuse” of the code is increased.
    2. It also makes it possible to hide the platform-specific implementation from the end user.
    3. One of the main purposes of this pattern is to provide a mechanism for implementing binary library compatibility when changing its implementation (this is achieved due to the fact that the entire implementation is in a private class). More information 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 on binary compatibility from KDE developers of Binary Compatibility Issues With C ++ with a short collection of what you need and what cannot be done to maintain 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 of course the smaller size.
    5. The build speed of the application is increasing (which is very important).
    6. All unnecessary implementation is hidden from the client, unlike 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). A mechanism whereby copying classes does not copy data, and copying occurs only when copies of the class need to change this data. Implicit-sharing is implemented in all Qt container classes. For its implementation, a Pimpl implementation called “shared D-pointers” is used. In general, this is a capacious topic and requires a separate article.


    But there have to be minuses, I won’t hide them, I will lay them out as is:
    1. Each constructor, destructor, causes memory allocation and deallocation, which increase the class creation time.
    2. Each method that contains access to a 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 large quantities in the code and created / deleted during the life of the program is inappropriate. For example, loading and storing certain data can serve as an example of such an implementation; here, classes that implement this data are best made as simple as possible. And any change in such classes suggests that the protocol for the transfer of this data itself is changing. But there may be other examples of such classes. Therefore, the basic rule when using any pattern applies to this one too: it is necessary to use it optimally, where it is really necessary.
    That is, this pattern must be used if you are writing a library or plan to move this functionality into a separate library in the future. In other cases, in the author’s personal opinion, the use of this approach is redundant.

    How it is implemented in Qt libraries.


    Qt code uses the d-pointer approach. The point is that the XXXPrivate class and the public class variable are declared in the protected section. An 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 along both public and 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: qclassname_p.h is added. And these classes are not installed 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 benefits of Qt's d-pointers approach?


    This approach may seem a bit confusing at first glance, but I assure you that it is really very simple and intuitive, and even facilitates code readability (I attribute this property to subjective, so it’s debatable, it all depends on the perception of a particular person).
    Advantages:
    1. Simple and intuitive.
    2. Direct access to the entire hierarchy of private classes (in reality they are not private :-), but protected).
    3. The ability to access the public class from the private.
    4. The ability to implement a system of signals and slots for a private class and hide them from external use using the Q_PRIVATE_SLOT macro (topic for a separate article).

    I want the same, but with mother-of-pearl buttons! (c) The 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 you need to do:
    1. Make forward - declare your private class before declaring a public class:
    class MyClassPrivate;
    class MyClass: public QObject
    { ..............

    2. Next, 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;

    Please note that the pointer is constant, in order to avoid any random absurdities there.
    3. Also in the protected section (both 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, declare a macro access to the d-pointer:
    Q_DECLARE_PRIVATE(MyClass);

    Now let's see what it is:
    #define Q_DECLARE_PRIVATE(Class) \
      inline Class##Private* d_func() { return reinterpret_cast(d_ptr); } \
      inline const Class##Private* d_func() const { return reinterpret_cast(d_ptr); } \
      friend class Class##Private;

    As we see here, the function d_func () and d_func () const are declared, with which we get a pointer to a private class and constant private class, respectively. And we get it already cast to the type of private class of this object. Well, we declare our private class a friend for 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 d_func function remains unchanged.
    The macro implementation looks like this:
    #define Q_DECLARE_PRIVATE_D(Dptr, Class) \
      inline Class##Private* d_func() { return reinterpret_cast(Dptr); } \
      inline const Class##Private* d_func() const { return reinterpret_cast(Dptr); } \
      friend class Class##Private;

    5. Now you need to declare our private class in a .cpp or _p.h file. If you expect further inheritance from your class or intend to use private slots, you need to put everything in a separate _p.h file. If not, then 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 files that include it. This can generally be taken as a rule, as it makes the work of the compiler easier.
    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 the topic of a separate article, and such articles have already been written enough, and of course, if you do not believe or do not trust the Internet, then turn to Straustrup, he discusses this topic in detail.
    7. The constructor implementation from the protected section will look something like this:
    MyClass::MyClass(MyClassPrivate &dd, QObject* parent)
           :QObject(parent)
           ,d_ptr(&dd)
    { .....

    Well, a regular constructor with such an declaration (pay attention to the explicit keyword, if you do not know what it is and why, take an interest - 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 heir transfers an instance of its private class to all base classes along the chain of the inheritance hierarchy (it is also arranged in the Qt class hierarchy; the very first class in the private class hierarchy is QObjectData, which contains the parent, the state of the object, and other base properties).
    8. To access an instance of a private class from a public class method, there is a macro Q_D (). Here 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 the variable "d" (here it is a D-pointer :-) !!! )
    int MyClass::foo() const
    {
      Q_D(const MyClass);
      return d->i;
    }

    Note that in constant methods it is necessary to add const in front of the class name in the Q_D macro in order to get a constant pointer to a constant instance of a private class (if this formulation scared 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 a Q-pointer) is the same D-pointer (aka a D-pointer), only exactly the opposite. It serves to access from methods of the private class to the public instance (it is usually used in cases where the logic is also transferred to the private class, or it is planned to do this later on in the hierarchy chain).
    To implement it, you must declare a pointer variable to the base class in the very first class of the private hierarchy:
    class MyClassPrivate
    {
    public:
      MyClassPrivate();
      virtual ~MyClassPrivate();

      int i;
      MyClass *q_ptr;
    }

    And in all hierarchy classes, declare the Q_DECLARE_PUBLIC macro, in which you plan to use the Q-pointer.
    class MyClassPrivate
    {
      Q_DECLARE_PUBLIC(MyClass);
    public:
      MyClassPrivate();
      virtual ~MyClassPrivate();

      int i;
      MyClass *q_ptr;
    }

    Here's what the Q_DECLARE_PUBLIC macro is:
    #define Q_DECLARE_PUBLIC(Class)                  \
      inline Class* q_func() { return static_cast(q_ptr); } \
      inline const Class* q_func() const { return static_cast(q_ptr); } \
      friend class Class;

    As you can see, everything is the same as in Q_DECLARE_PRIVATE, except for the names. Well, there’s no macro for the alternative name q_ptr, like Q_DECLARE_PRIVATE_D.
    Important: do not forget to take care of the initialization of the q_ptr variable in all constructors of the first class of the public hierarchy:
    MyClass::MyClass(QObject * parent)
         :QObject(parent)
         ,d_ptr(new MyClassPrivate())
    {
      Q_D(MyClass);
      d->q_ptr = this;
    .......
    }


    10. For access from methods of a private class to a public class (for example, to make a call to a public class signal) there is a macro Q_Q, here is what it looks like:
    #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



    Keep in mind that all these macros are not part of the public API, and can be changed at any time. But I can reassure you. Firstly, all this is such a basic foundation that at least it will change to the new major version, but in this case you 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 you don’t trust anyone, you can declare similar macros in your global file by changing their name and using them in your code, then there’s nothing to be afraid of (except changing the behavior of compilers in relation to macros :-), because the present paranoid always have 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 apart and do not interfere with each other. Since I have overridden the d_ptr variable in my class, and for all descendants of my class, d_ptr will be a pointer to my hierarchy, but not for QObject. For him, d_ptr will be a Qt hierarchy of private classes (more precisely, a pointer to QObjectPrivate).
    A reasonable question may arise, why not inherit our private class from QObjectPrivate. Answer: it is possible, but firstly binary compatibility with the Qt library will be lost (it will be necessary to have our library for each version of the private implementation in Qt (it also changes, QWidget for sure)). And the second argument against this is that private libraries will be required to build our library Qt library header files, which are located in the src source folder, 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 is eaten with it.
    Rules for designing code in Qt-style.
    How to implement Implicit Sharing and what is a Shared D-pointer.
    Useful macros in QT.
    Qt Creator inside and out.
    How to write an application for 7 platforms with minimal effort.
    And a lot of other interesting things.

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

    PPS: I apologize for my Russian language, possible incorrect speech and errors (both spelling and syntactic) - this is not my native language :-)

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

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


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


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


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


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


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

    Also popular now: