If you build a library in C++ and make it available for other people, you should know about the pimpl idiom. This pattern makes it possible to encapsulate your real implementation from your API and won’t break binary compatibility (ABI).

So what is the problem about the “normal” C++ code? Link to heading

If you build an API class (which can be used by the consumer of the library), you should first think about binary compatibility. Every binary break is hard and frustrating for the consumer because they have to recompile the whole source code due to the compatibility break. Adding new functions to a “normal” C++ code can be a difficult job if you want (and you should) to keep binary compatibility. Most developers do not know that adding or deleting private members can also break ABI, just as changing public functions/members can. If you are new to creating C++ APIs, you should read some books or blogs about creating a good API (I prefer this book) – but if you don’t want to read a whole book, you could read this blog post 😉.

How can I design a class to be as binary compatible as possible? Link to heading

One technique in C++ is the Pimpl idiom (“pointer to the implementation”). It is simple but extremely effective.

It is based on two facts:

  • A pointer to an object always has the same size (on the same compiler version and platform).
  • Class-forwarding makes it possible that you don’t have to include the header file for the forwarded class.

An API class has two parts:

  • The API methods (in your class).
  • The Pimpl idiom class (a private pointer to the instance) which holds the real implementation.

MyClass.h

class MyClass
{
    class Impl;
    Impl* _Impl;
public:
    MyClass();
    ~MyClass();
    void MyPublicFunction(int num);
};

MyClass.cpp

// The first class is our Pimpl idiom class.
class MyClass::Impl // The Pimpl class is a nested class because we don't want the user to be able to reuse this class.
{
public:
    void MyImplFunction(int num)
    {
        Num = num;
    }
    int Num;
}
 
// the API class
MyClass::MyClass():
_Impl(new Impl())
{}

MyClass::~MyClass()
{
    if(_Impl)
    {
        delete _Impl; // don't forget to delete the pimpl-object
    }
}
void MyClass::MyPublicFunction(int num)
{
    _Impl->MyImplFunction(num);
}

Now it is possible to add/change methods and members to the Impl class without being afraid of breaking the binary code of the API class because the Impl is always a pointer (same size). You can also switch the complete backend code (for example, use another class as the Pimpl class)!

If you have a C++11 compiler (or use Boost or Qt), you can use a unique/shared pointer around your Pimpl idiom class => so you don’t need to delete your object yourself, which makes the code much cleaner and safer.

MyClass.h in C++ 11 style

#include <memory> // required for std::unique_ptr

class MyClass
{
    class Impl;
    std::unique_ptr<Impl> _Impl;
public:
    MyClass();
    ~MyClass(); // if you use a uniqued_ptr you have declare an Destructor
    void MyPublicFunction(int num);
};

When using the Pimpl idiom, you have to consider one thing: the copy constructor. If you don’t override the copy constructor, C++ will only make a copy of the pointer address and not a clone of the whole object behind this pointer. This can lead to a big problem where you can have multiple objects referring to the same object. If you delete all your objects, it will crash because the first delete is okay, but a second delete will not work since the object is already deleted!

You have two options:

  • You can implement a copy constructor to make a good copy.
  • You can disable the copy constructor (do a little Google search if you need that) so nobody can make a copy.

In C++11, there is a small benefit to disabling the copy constructor. The std::unique_ptr will throw a compile error if you try to copy it => so the copy constructor is disabled by default if you use std::unique_ptr.

If you use a naked pointer or the std::shared_ptr template, you have to implement the copy constructor yourself to avoid problems!

One little note about using std::unique_ptr as a member: You need to define the destructor yourself. The destructor could be empty!

Pros Link to heading

  • Hides the private part of the class
  • The binary compatibility ABI isn’t broken if you change something in the Pimpl idiom class
  • Fewer header dependencies => because most headers now only need to be in the *.cpp
  • Faster compilation times

Cons Link to heading

  • The Pimpl idiom could slow down your code (because there is always a pointer access), but there are some techniques to speed up a Pimpl idiom.
  • Extra heap allocation