Pimpl your C++ code
We’ll take a look at the PIMPL (private implementation) pattern today, which is especially useful for larger projects, where compile times become a problem. Pimpl allows to decouple the interface from the implementation, to a point where nearly each class can be fully forward declared only. This reduces the compile times dramatically. Another usage of Pimpl is to hide large or ugly include files (windows.h, anyone?) from the clients.
So how does it work? The idea is to forward define an inner class, and always store a pointer to it. Let us take a look at an example:
#include <boost/tr1/memory.hpp>
class Container
{
public:
Container (const size_t size);
Container (const Container& other);
Container& operator =(const Container& other);
int& operator [] (const int index);
const int& operator [] (const int index) const;
private:
class Impl;
std::tr1::shared_ptr<Impl> impl_;
};
This is our public class interface, and see, we don’t expose our container type. In this case, we’ll use a standard vector. Our implementation looks like this:
// Implementation
#include <vector>
class Container::Impl
{
public:
Impl (const size_t size)
{
vec.resize (size);
}
std::vector<int> vec;
};
Container::Container (const size_t size)
: impl_ (new Impl (size))
{
}
/*
We need those copy constructors, otherwise, we would
share our state. For most classes, it is best to make them
noncopyable anyway.
*/
Container::Container (const Container& other)
: impl_ (new Impl (other.impl_->vec.size ()))
{
impl_->vec = other.impl_->vec;
}
Container& Container::operator = (const Container& other)
{
impl_->vec = other.impl_->vec;
return *this;
}
int& Container::operator [] (const int index)
{
return impl_->vec [index];
}
const int& Container::operator [] (const int index) const
{
return impl_->vec [index];
}
That’s it! We have to pay by one additional memory allocation (which can be circumvented by semi-portable trickery), but we gain a lot while compiling. A large library which makes excessive use of Pimpl is Qt, but it pays off, as it includes the bare minimum required to get a compile, and nothing more. For the sake of completeness, a small usage example:
#include "container.h"
#include <iostream>
int main ()
{
Container c (23);
c [13] = 37;
std::cout < c [13] < std::endl;
Container copy = c;
copy [13] = 4711;
std::cout < c [13] < std::endl;
}