Qt-HowTo: Private Classes and D-Pointers

If you have ever looked at the Qt source code, you have probably noticed that they use private classes and d-pointers. At first time when I saw this, I thought that what the heck is that thing and what are all these Q_D() and Q_Q() macros? I am probably not the only one that was bit confused about what they really do and what do you gain by using them. This is one reason why I decided to write a small article of using d-pointers. Most of the stuff in this article is based on KDE TechBase documentation.

Before continuing let’s take a look at how the code looks when using d-pointers inside methods.

...
void MyClass::setFoo( int i )
{
    Q_D(MyClass);
    d->m_foo = i;
}

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

In the example above, there are two methods. One for setting the value and another one for getting the value from the internal member of the class. Usually you would introduce an instance variable (int m_foo;) in a private section of the class MyClass and then you modify or return the value of it. In the example above, you set the integer ‘i’ to the  instance member m_foo of the private class. Next you might ask what do you gain of using this approach instead of using a “traditional way”, as it’s done in the most of the C++ code.

Binary Compatibility and Other Benefits

There is a trick with this approach – using a d-pointer you can maintain binary compatibility easier. There are also other benefits especially when using shared d-pointers. With d-pointers you have a smaller memory footprint of your main class, but if using shared d-pointer then calling a private class methods makes the library load time faster. This is the one reason why it is recommended that you move as much code as possible to the private class. Back to the binary compatibility. If you don’t know what binary compatibility means, in short it means:

“A library is binary compatible, if a program linked dynamically to a former version of the library continues running with newer versions of the library without the need to recompile.” by KDE techbase

For Qt this is especially important because it is a widely adapted framework and the binary compatibility is very important to maintain. At least for application developers, it saves a lot of effort when there is a new Qt release available and you don’t necessarily  need to rebuild your application. (Of course this is not possible always) Another reason, beside avoiding ABI breaks, is that using d-pointers makes class’ public interface look cleaner because you don’t need to define all the private members in the header. For large classes, the amount of private members can be quite huge.

There are excellent explanations of d-pointers and binary compatibility in KDE’s Techbase. Take a look at that if you are more interested in about binary compatibility and the other benefits of using d-pointers. There is also a great list of what you can do and what you shouldn’t do if you want to maintain the binary compatibility.

Using Private Classes and D-Pointers

Let’s have a simple example of two different header files.

//////////// Traditional way: //////////// 

class MyObject: public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)

public:
    MyObject();
    virtual ~ MyObject();
    enum Priority { High, Low, VeryHigh, VeryLow };
    void setPriority(Priority priority);
    Priority priority() const;
    void setMemberX( int x );
    int memberX() const;
    void setMemberY( double y);
    double memberY() const;

Q_SIGNALS:
    void priorityChanged( MyObject::Priority priority );

private:
    Priority m_priority;
    int        m_memberX;
    double m_memberY;
};

//////////// D-pointer way: ////////////

class MyObjectPrivate;
class MyObject: public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)

public:
    MyObject();
    virtual ~ MyObject();
    enum Priority { High, Low, VeryHigh, VeryLow };
    void setPriority(Priority priority);
    Priority priority() const;
    void setMemberX( int x );
    int memberX() const;
    void setMemberY( double y);
    double memberY() const;

Q_SIGNALS:
    void priorityChanged( MyObject::Priority priority );

protected:
    MyObjectPrivate * const d_ptr;

private:
    Q_DECLARE_PRIVATE(MyObject);
};

The example is quite simple, but it demonstrates the difference in a class declaration if using a traditional way to define a class and all of its members instead of using d-pointer approach. If using the traditional way, you usually introduce all the private members in a private section of your class. In practice the list of private member can be quite long and from a client’s perspective the private section is not even interesting.

When using the d-pointer approach, like in the latter example, you will gain much nicer looking interface. You don’t need to “publish” your private section to the rest of the world, because all the private members are defined in a private class (MyObjectPrivate). If you want to change the way how private members are stored and handled, you don’t need to touch the header at all. In that case you change the implementation of the private class only.

There are cases when you need to define your private class in a separate header file for example when your private class needs to define signals and slots. You should never define private class in the same header file with the main class. The usual convention is that then you name a header file of the private class in a following way: myclass_p.h. Remember NOT to copy private headers to the include directory e.g. to /usr/include if you are providing a development package of your library. Instead use forward declaration in the header file of the main class as you can see from the examples .

class MyClassPrivate;

Forward declaration means that you don’t need to include a header, instead you “fool” a compiler to think that this type has been declared in the same header file. This shortens the compilation time when building large projects. You can use forward declaration for references and for pointers.

Magic Macros

The private section is where all the magic happens when using d-pointers. I introduce  MyClassPrivate const * d_ptr pointer there. If you want to be able to inherit the members of the private class, then you need to introduce d_ptr in the protected section and inherit MyClassPrivate to the private class of the inheriting subclass. In the example, I use “const” keyword to prevent d_ptr to be reassigned once it has been initialized.

private:
     MyClassPrivate * const d_ptr;
     Q_DECLARE_PRIVATE(MyClass);

The macro Q_DECLARE_PRIVATE(Class) hides all the ugly stuff. What does it do then? It creates an inline function called d_func() which returns an instance of your private class via d_ptr. It also creates an inline function which returns const pointer to your private class. The last but not the least, it makes the private class a friend class of your main class. If you noticed from the very first example, you can use Q_D macro in two ways: Q_D(Class) or Q_D(const Class).

#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
    friend class Class##Private;

In the first example I showed you how you can access to the d_ptr via Q_D(Class) macro. The Q_D(Class) macro returns the “d”-pointer via d_func() to the d_ptr that you have defined in your header. Check the code below to see how Q_D(Class) macro is defined.

// A template function for getting the instance to your private class instance.
template  static inline T *qGetPtrHelper(T *ptr) { return ptr; }

// A macro for getting the d-pointer
#define Q_D(Class) Class##Private * const d = d_func()

Basically this is what you need to know if you want to use d-pointers. Wait.. there is still something that you need to know more.

Using Q-Pointers

As I said earlier, it is highly recommended that most of the code is in the private class instead of the main class. This might lead you to the situation where you need to be able to access to the main class members from the private class. In this situation you can use q-pointer. What is a q-pointer then? It’s like a d-pointer but it works in opposite direction. Let’s have an example:

class MyObjectPrivate
{
public:
    MyObjectPrivate(MyObject * parent):
            q_ptr( parent ),
            m_priority(MyObject::Low)
    {}
    void foo()
    {
        // Demonstrate how to make MyObject to emit a signal
        Q_Q(MyObject);
        emit q->priorityChanged( m_priority );
    }

    //  Members
    MyObject * const q_ptr;
    Q_DECLARE_PUBLIC(MyObject);
    MyObject::Priority m_priority;
};

In the private class (MyObjectPrivate) the main class instance (MyObject) is passed via constructor to the q_ptr. As in the main class we use macro but this time it’s called Q_DECLARE_PUBLIC(Class). What it does is that you can get access to the main class instance via Q_Q(Class) macro, but this time instead of d-pointer you must use q-pointer. In the example above, the private class makes the main class to emit a signal, which is quite convenient way to do that. Another, but uglier way to do the same thing, is to use signal slot mechanism between private and the main class, but in my opinion, it makes the implementation more complicated.

Conclusion

When I first time saw how Qt uses d-pointers, it took some time to figure out how it really works. Good knowledge base for getting more information about d-pointers is KDE’s TechBase and you can read the most of the things that this article contains also from there. They only write better:). Another knowledge base is the source code of Qt. With Qt Creator it’s quite easy to index the whole Qt code base and make searches there.

Why I was writing about this topic then? I think that the usage of d-pointers can really help to provide a code that is easier to maintain and in my opinion it makes the public interface to look nicer. Only thing that you need to get used to of, is the usage of the macros. Btw, if you are not developing your app or your library based on Qt, nothing prevents you to provide your own d_func() or macros to hide the ugly stuff. So in general, I think using a d-pointers is a good way to develop software, not only a Qt way to do that.

The negative thing is that there is no documentation for this from Qt side. At least I couldn’t find anything related on that from Qt Creator’s help. I think that many Qt developers started to use this approach if they would know about it. So if there are any Trolls that are reading this, would it be possible to make a small guideline for using d- and q-pointers?

Thanks for reading.

Tags: , , , , ,

8 Responses to “Qt-HowTo: Private Classes and D-Pointers”

  1. pepavondepo says:

    What’s wrong with public interfaces (pure abstract class), which serves as base for inherited private implementation?

    • zchydem says:

      @pepavondepo

      Do you mean that the functionality that MyClassPrivate provides would be inherited privately to the subclass (MyClass)?

      I didn’t quite get what do you mean this approach? Can you provide an example?

    • kypeli says:

      @pepavondepo

      If I understood you correctly, I am sure you can do that too from a technical point of view. But if you ask what is wrong with that in comparison to Qt’s d_func() macros is that Qt’s way is more elegant, straight forward and hides a lot of the ugliness from the developer itself. I mean, why would you do something yourself that Qt can already provide for you :)

  2. ctarsoaga says:

    in c++, pimpl does more in some cases than interfaces + private classes do

    pimpl can (also) remove the need for data members! => cleaner interface, faster compile times…

    as soon as you use aggregation / add a member (no matter if it points to a pure! interface or not) into a class inside a c++ header, pimpl can be your friend

  3. cymurs says:

    Thanks for writing!
    I gain better knowledges.

Leave a Reply

You must be logged in to post a comment.