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;
}
Related posts:
Why do you use there boost::shared_ptr instead of std::auto_ptr ?
Because std::auto_ptr is dangerous
If you use std::auto_ptr, and forget to implement the copy-constructor, the first copy using std::auto_ptr will destroy the source. With shared_ptr, you get shared state, which is much less of a problem (read: it won’t crash).
The second part of this blog post is just plain wrong, there is no such keyword as “export template” and the “export” keyword is only implemented by Comeau and Digital Mars compilers, not by MSVC, GCC, Intel.
There is a keyword “extern template” but it just says that the compiler shouldn’t generate code for that template instantiation in the current translation unit (expecting it to already have been generated in another one). This helps out compilers which aren’t smart enough to do comdat folding at linktime.
@John Doe
Either you commented at the wrong blog, or at least at the wrong entry, as there is no export template here at all. If you mean the “C++ tricks, #6: Explicit template instantiation” entry, I use
extern templatethere since the beginning; all the code on my blog is actually tested with MSVC at least, and typically with GCC as well.