Why every C++ developer should know about the pimpl idiom pattern
If you build a library in C++ and make it available for other people you should know about pimpl idiom. This pattern make 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?
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 he/she has to recompile the hole source code because you have break the compatibility. Adding new functions to an “normal” C++ code could be a hard job if you want (and you should) to keep binary compatibility. Most developer does not know that adding or delete private member can also break ABI as changing public functions/members. If you are new in creating C++ API you should read some books/blogs about creating good API (i prefer this book) – but if you don’t want to read a hole book, you could read this blog-post 😉
So the question is:
How can i design a class which is binary compatible as much as possible?
One (and most used) technique in C++ is Pimpl idiom (“pointer to the implementation”). It is simple but extrem effective. It is based on two facts:
- a pointer to an object has always the same size (on the same compiler-version and platform)
- class-forwarding make it possible that you don’t have to include the header-file for the forwarded class
So a 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
// first class is our pimpl idiom class
class MyClass::Impl //the pimpl class is a nested class because we don't want that the user can 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 breaking binary-code of the API-class because the Impl is always a pointer (same size). You can also switch the complete backend-code (f.e. use another class as pimpl class)!
If you have an C++ 11 compiler (or use boost or Qt) you can use a unique/shared pointer around you pimpl idiom class => so you don’t need to delete your object yourself which makes that 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 you use Pimpl idiom you have to think about 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 hole object behind this pointer. This can lead into a big problem. You can have multiple object which refers to the same object. If you delete all your object it will crash because the first delete is ok, but a second delete will not work because the object is already deleted!
So you have two options:
- You can implement a copy-constructor to make a good copy
- You can disable the copy-constructor (make a little google search if you need that) so nobody can make a copy
In C++ 11 there is a little benefit about disabling the copy-constructor. The unique_prt will throw an compile-error if you try to copy it => so the copy-constructor is disabled by default if you use unique_prt.
If you use a naked pointer or the shared_prt-template you have to implement the copy-constructor yourself to avoid problems!
One little note about using std::unique_prt as a member: You need to define the destructor yourself. The deconstructor could be empty!
Pros
- Hide the private part of the class
- The binary compatibility ABI isn’t broken if you change something in the pimpl idiom class
- Lesser header dependencies => because most header now only needs to be in the *.cpp
- Faster compilation times
Cons
- The pimpl idiom could slow down your code (because there is always a pointer access) but there are some technique to speed up a pimpl idiom
- Extra heap-allocation