What is the d-pointerIf you have looked into Qt source files like this one [qt.gitorious.org], you will find it generously sprinkled with
Binary compatibility, what is that?
When designing libraries like Qt, it is desirable that applications that dynamically link to Qt continue to run without recompiling even after the Qt library is upgraded/replaced with another version. For example, if your application CuteApp was based on Qt 4.5, you should be able to upgrade the Qt libraries (on Windows shipped with the application, on Linux often comes from package manager automatically!) from version 4.5 to Qt 4.6 and your CuteApp that was built with Qt 4.5 should still be able to run.
What breaks binary compatibility
So, when does a change in the library require a recompilation of the application? Let’s take a simple example:
Here we have a Widget that contains geometry as a member variable. We compile our Widget and ship it as WidgetLib 1.0.
For WidgetLib 1.1, someone comes up with the bright idea to add support for stylesheets. No sweat, we just add new methods and add a new data member.
We ship WidgetLib 1.1 with the above change only to find that CuteApp that was compiled and ran just fine with WidgetLib 1.0 crashes gloriously!
Why did it crash?
The reason is that by adding a new data member, we ended up changing the size of Widget and Label objects. Why does this matter? When your C++ compiler generates code, it uses ‘offsets’ to access data within an object.
Here’s an oversimplified version of how the above POD objects might look in memory.
|Label object layout in WidgetLib 1.0||Label object layout in WidgetLib 1.1|
|m_geometry <offset 0>||m_geometry <offset 0>|
|———————||m_stylesheet <offset 1>|
|m_text <offset 1>||————————-|
|———————-||m_text <offset 2>|
Never change the size of an exported C++ class
In summary, never ever change the size or layout (don’t move the data around) of exported (i.e visible to the user) C++ classes once your library has been released. A C++ compiler generates code assuming that the size or the ordering of data in a class does not change after the application has been compiled.
So, how can one not change the size of the object and yet add new features?
The trick is to keep the size of all public classes of a library constant by only storing a single pointer. This pointer points to a private/internal data structure that contains all the data. The size of this internal structure can shrink or grow without having any side-effect on the application because the pointer is accessed only in the library code and from the application’s point of view the size of the object never changes – it’s always the size of the pointer. This pointer is called the d-pointer.
The spirit of this pattern is outlined in the code below (all code in this article doesn’t have destructors, of course you should add them in real code).
widget_p.h, which is the private header file of the widget class
Next, there’s an example of a child class based on Widget.
With the above structure, CuteApp never accesses the d-pointer directly. And since the d-pointer is only ever accessed in WidgetLib and WidgetLib is recompiled for every release, the Private class can freely change with no impact on CuteApp.
Other benefits of d-pointer
It’s not all about binary compatibility. The d-pointer has other benefits:
- Hide implementation details – We can ship WidgetLib with just the header files and the binaries. The .cpp files can be closed source.
- The header file is clean of implementation details and can serve as the API reference.
- Since the header files needed for implementation are moved from the header file into the implementation (source) file, compiles are much faster.
It is indeed true that the above benefits appear trivial. The real reason to use d-pointers in Qt is for binary compatibility and the fact that Qt started out closed source.
The q-pointerSo far, we have only seen the d-pointer as a C-style data structure. In reality, it contains private methods (helper functions). For example,
Next, another class based on Widget.
Inheriting d-pointers for optimizationIn the above code, creating a single Label results in the memory allocation for
This is solved by having an inheritance hierarchy for our private classes and having the class getting instantiated pass on a the d-pointer all the way up.
Notice that when inheriting d-pointers, the declaration of the private class has to be in a separate file, for example widget_p.h. It’s no longer possible to declare it in the widget.cpp file.
d-pointers in QtIn Qt, practically every public class uses the d-pointer approach. The only cases where it’s not used is when it is known in advance that the class will never ever have extra member variables added to it. For example, for classes like
Q_D and Q_QA side effect of the optimization that we did in the previous step is that the q-ptr and d-ptr are of type
Hence, when accessing the d-pointer in a subclass, we need to static_cast to the appropriate type.
As you can see, it’s not pretty having static_cast all over the place. Instead, there are two macros defined in src/corelib/global/qglobal.h which make it straighforward:
Q_DECLARE_PRIVATE and Q_DECLARE_PUBLICQt classes have a
This macro can be used this way:qlabel.h
The idea is that