GotW #101: Compilation Firewalls, Part 2 (Difficulty: 8/10)

Guru Question

GotW #100 demonstrated the best way to express the Pimpl idiom using only standard C++11 features:

// in header file
class widget {
public:
    widget();
    ~widget();
private:
    class impl;
    unique_ptr<impl> pimpl;
};

// in implementation file
class widget::impl {
    // :::
};

widget::widget() : pimpl{ new impl{ /*...*/ } } { }
widget::~widget() { } // or =default

Is it possible to make the widget code easier to write by wrapping the Pimpl pattern in some sort of library helper? If so, how? Try to make the widget code as convenient and concise as possible to write, with any compiler-generated semantics either correct by default or producing compile-time errors if the widget author forgets to write them.

Solution

Yes. There are several approaches, but perhaps the one that’s simplest to use is a helper similar to a unique_ptr, but I’ll call it pimpl instead of pimpl_ptr because it’s also going to be responsible for doing the allocation and deallocation of the impl object.

Let’s start with our goal, namely what we’d like the simplified widget code to look like. Since the Pimpl Idiom affects both the header file, where the visible class is defined, and the implementation file, where the private parts are hidden, we put the corresponding Pimpl boilerplate into two corresponding files as well.

First, we bring in pimpl_h.h and add a data member of type pimpl<myclass::impl>. Then in our implementation file we bring in pimpl_impl.h and define our impl class.

// in header file
#include "pimpl_h.h"
class widget {
    class impl;
    pimpl<impl> m;
    // ...
};

// in implementation file
#include "pimpl_impl.h"
class widget::impl {
    // ...
};

Why is this an improvement over the hand-rolled Pimpl Idiom?

  • First, the code is simpler because it eliminates some pieces of boilerplate: In the hand-rolled version, you also have to declare the constructor and write its body in the implementation file and explicitly allocate the impl object. You also have to remember to declare the destructor and write its body in the implementation file, for obscure language reasons explained in #100.
  • Second, the code is more robust: In the hand-rolled version, if you forget to write the out-of-line destructor, the Pimpl’d class will compile in isolation and appear to be in a check-in-able state, but will fail to compile when used by a caller that tries to destroy an object and encounters a helpful “cannot generate destructor because impl is, uh, you know, incomplete” error that leaves the author of the calling code scratching his head as he walks over to your office to ream you out for checking in something broken.

For the pimpl<T> template itself to make all this work, however, puts it in a little bit of a pickle: It has to sit in the middle between our visible class and our actual implementation without knowing the definition of either one, and it has to wire everything together in a way that is neutral to whatever the visible and Pimpl classes might want to do. Fortunately, this is nothing that pimpl<T> isn’t able to handle.

Now let’s turn to see what pimpl_h.h and pimpl_impl.h have to do in order to wire us up.

pimpl_h.h: What the Visible Class Needs

pimpl_h.h provides the opaque impl type and its pointer (here called m instead of pimpl). It also declares two constructors and a destructor, which will be defined out of line in pimpl_impl.h and manage the impl object’s lifetime.

// pimpl_h.h
#ifndef PIMPL_H_H
#define PIMPL_H_H

#include <memory>

template<typename T>
class pimpl {
private:
    std::unique_ptr<T> m;
public:
    pimpl();
    template<typename ...Args> pimpl( Args&& ... );
    ~pimpl();
    T* operator->();
    T& operator*();
};

#endif

The second constructor exists to provide a way to pass initialization values through to the impl object. Since pimpl itself knows nothing about the impl type, it’s perfect forwarding to the rescue! This is an interesting idiom that will become increasingly common and familiar: You take all parameters by && rvalue reference, and then pass them along using std::forward, which brings us to pimpl_impl.h…

pimpl_impl.h: Providing the Internals

pimpl_impl.h provides the rest of the internal implementation to create and destroy the impl object. As promised, here’s where we see the rest of the perfect forwarding implementation.

First, bonus points if you got the out-of-line forwarding template declaration syntax right. Notice that for that constructor we need not one, but two template clauses – one for the pimpl class template parameter, and one for the constructor’s passed-through arguments.

// file pimpl_impl.h
#ifndef PIMPL_IMPL_H
#define PIMPL_IMPL_H

#include <utility>

template<typename T>
pimpl<T>::pimpl() : m{ new T{} } { }

template<typename T>
template<typename ...Args>
pimpl<T>::pimpl( Args&& ...args )
    : m{ new T{ std::forward<Args>(args)... } } { }

template<typename T>
pimpl<T>::~pimpl() { }

template<typename T>
T* pimpl<T>::operator->() { return m.get(); }

template<typename T>
T& pimpl<T>::operator*() { return *m.get(); }

#endif

Each constructor creates a new impl object without having to know any details of its type. The type just has to be defined somewhere, by the caller.

The forwarding constructor simply forwards its parameters onward, so that the pimpl<widget>::impl type can happily provide a constructor that takes any kind of initialization parameters, and the visible widget constructor could accept those parameters and then pass them safely, and accurately, through to the impl, all without pimpl<> itself needing to be any the wiser. All the necessary “wisdom” is provided by the standard in the form of && and std::forward – it’s a nicely standardized way of keeping the left hand from needing to know too much about the right hand’s business, and it works very well indeed.

.

Notes

Variadic templates make the forwarding constructor easy to write in one shot as a single constructor template. If you are using a compiler that supports move semantics but doesn’t have variadic templates yet, you can still get the same effect, just with a bit of repetition – instead of a single constructor variadic template, write an overload set of multiple constructor templates, one for each number of parameters you want to support. The first three would look like this:

template<typename T>
template<typename Arg1>
pimpl<T>::pimpl( Arg1&& arg1 )
    : m( new T( std::forward<Arg1>(arg1) ) ) { }

template<typename T>
template<typename Arg1, typename Arg2>
pimpl<T>::pimpl( Arg1&& arg1, Arg2&& arg2 )
    : m( new T( std::forward<Arg1>(arg1), std::forward<Arg2>(arg2) ) ) { }

template<typename T>
template<typename Arg1, typename Arg2, typename Arg3>
pimpl<T>::pimpl( Arg1&& arg1, Arg2&& arg2, Arg3&& arg3 )
    : m( new T( std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3) ) ) { }

Major Change History

2011-12-06: Changed pimpl<> to be used as a member instead of as a base class — I really hate needless inheritance coupling, and you should too.

2011-12-13: Added explanation for why this is an improvement over implementing the idiom by hand.