Blog Series Tags

The pImpl Idiom – Hiding Private Implementation Details

One key principle behind the design of classes is that of the encapsulation of the implementation from the clients using the code. Here a client is any bit of source code that uses the classes that you write. C++ provides an imperfect implementation of this idea in the way it defines rules for classes. Consider the following class which is declared in event.hpp.

#include "system_clock.hpp"
 
class Event
{
 
private: 
    SystemClock mClock;
    std::string mMessage;
 
public:
    Event(std::string message);
    ~Event();    
};

The Event class models an event that can happen at some point in time. It uses “system_clock.hpp” which defiles a class called SystemClock to see what time it is. Notice that both the message and the current time as defined by the member variable mClock in Event is private. This should mean ideally that any changes to the variables in the private section of the class is not exposed to the client code and indeed to an extent it is. No client can create an instance of Event and then access it’s private members like so:

Event someEvent("Something happened!");
someEvent.mClock = SystemClock(); // Not allowed.

This does not stop client code from seeing those members however. This means that whenever a client includes the event class like so:

#include "event.hpp"

it also includes “system_clock.hpp” which is included by “event.hpp” which is clearly not a requirement as the Event class never returns an object of type SystemClock. This is the weakness of C++’s class member access. It ensures that clients cannot access private members but clients “see” – the private members because of the nested includes in the header that the client pulls in. This causes unneeded dependencies between clients and the implementation classes and leads to longer compilation time if you change private members of the class.

Consider for an instance that the Event class now requires higher resolution timing. For that you use a highres_clock (defined in the header file “highres_clock.hpp” instead of the standard system clock. You will now change the Event class by changing it’s header (event.hpp) as follows:

#include "highres_clock.hpp"
 
class Event
{
private:    
    HighresClock mClock;

Ideally this should just require the recompile of your library code and not clients that include it, however in this case since the library code sees which clock your using for implementation (as it sees the change in event.hpp) the compiler has to recompile every bit of client code that includes “event.hpp” as well.

It’s particularly nasty if your building libraries meant to be reused by others using third party libraries for part of the implementation. For example of you used the boost libraries asio module for handling some IO, if you include headers from the library in your header files it means that clients of your code will have to download boost in order to satisfy the compiler as it crawls the header file dependencies even though none of the boost types are used publicly.

It’s worth noting as this point that the reason C++ requires the private members to be listed in the class declaration is because it needs to know how much space to allocate for the members. By declaring mClock to be of type SystemClock and mMessage to be of type std::string your actually telling the compiler how much space a given Event object requires in memory. Similarly an object of type SystemClock will require a certain amount of memory, based on what it contains – 30 or 40 bytes shall we say for example? However a pointer to a SystemClock object will always require either 4 or 8 bytes of memory (depending on if it’s a 32 bit or 64 bit version of the program your compiling for) as it’s of a “pointer” type to the object and not the object itself and this is the distinction on which the pImpl (pointer to implementation) idiom is built on.

Instead of exposing private members to client classes by listing them in the private section of the class declaration we can define a pointer to a new class that holds the actual implementation details like so:

// event.hpp
#include <string>
 
class EventImpl;
 
class Event
{
 
private: 
    EventImpl* mImpl;
    std::string mMessage;
 
...

Notice that we have not yet defined the class EventImpl, we have only declared it’s existence. This is enough to create a pointer to it (because a pointer will always have the same amount of storage independent of the type it’s pointing to). Also notice we have only included the standard library string header, the custom clock headers are no longer included in the event.hpp file that clients will include. In event.cpp we define the private implementation class for Event like so:

// event.cpp
 
#include "event.hpp"
#include "highres_clock.hpp"
 
class EventImpl
{
private:
    friend class Event;
 
    HighresClock mClock;
     
    EventImpl(){}
 
    void SetTimestamp()
    {
        mClock = HighresClock();
    }
};

Notice two things. Firstly the include of the high resolution clock is now included in event.cpp file, which is not included by any external clients. Secondly notice that the EventImpl class has designated Event as a friend class. This means that the Event class can access private members of the EventImpl class – and this is required as all members of the EventImpl class are defined as being private. (Even though there is no public interface (declaration) for EventImpl the contents are listed as being private just to add that extra layer of protection – someone could make their own declaration if they were desperate enough).

Now in the constructor of the Event class we can instantiate an instance of the EventImpl class to handle all the truly private implementation details like so:

class Event
{
 
private: 
    EventImpl* mImpl;
    std::string mMessage;
 
    void Init();
 
public:
    Event(std::string message) :
        mMessage(message)
        {
            Init();
        }
 
...

Where the Init() function does the following:

void Event::Init()
{
    this->mImpl = new EventImpl();
    this->mImpl->SetTimestamp();
}

Notice that the Init method creates a new instance of EventImpl and then calls it’s SetTimestamp() function to set the new timestamp. This decouples clients of the Event class from the implementation detail of what kind of clock is used. This means that a client doing the following:

#include "event.hpp"

no longer pulls in the headers “system_clock.hpp” or “highres_clock.hpp” which pull in their own requirements.

On the down side writing code that keeps private bits private requires a bit more overhead as you have to write some extra boiler plate code for the implementation class and call methods inside the implementation class from the interface of the public class (Event::Init() calls EventImpl::SetTimestamp() to get it’s job done). You can avoid this by directly using the members of the implementation class because the public class is declared a friend but that’s not a good idea. Consider implementation classes that can have inheritance hierarchies of their own for example, something that might come in handy for creating subtle variations of the same functionality that can be accessed by the public class. Directly accessing the data members of the implementation class preempts such possibilities.

Finally we should avoid using new and delete for creating an instance of the EventImpl class. Instead we can use a smart pointer to better insure against memory leaks.

// event.hpp
#include <string>
#include <memory>
 
 
class EventImpl;
 
class Event
{
 
private: 
    // EventImpl* mImpl;
    std::auto_ptr<EventImpl> mImpl; // Less leaky
    std::string mMessage;
 
public:
    Event(std::string message);
    ~Event();
     
};
 
 
// event.cpp
 
#include "event.hpp"
#include "highres_clock.hpp"
 
class EventImpl
{
private:
    friend class Event;
 
    HighresClock mClock;
     
    EventImpl(){}
 
    void SetTimestamp()
    {
        mClock = HighresClock();
    }
};
 
Event::Event(std::string message)
    : mMessage(message), mImpl(new EventImpl())
{
    this->mImpl->SetTimestamp();
}
 
Event::~Event()
{
 
}

One final note, the destructor for ~Event should be defined after the body of EventImpl otherwise the compiler warns us:

Warning C4150: deletion of pointer to incomplete type ‘EventImpl’; no destructor called.

as the smart point at the point of destruction (inside ~Event) cannot know the destructor for the type EventImpl if that type has not been defined at that point.