Anteru's blog
  • Consulting
  • Research
    • Assisted environment probe placement
    • Assisted texture assignment
    • Edge-Friend: Fast and Deterministic Catmull-Clark Subdivision Surfaces
    • Error Metrics for Smart Image Refinement
    • High-Quality Shadows for Streaming Terrain Rendering
    • Hybrid Sample-based Surface Rendering
    • Interactive rendering of Giga-Particle Fluid Simulations
    • Quantitative Analysis of Voxel Raytracing Acceleration Structures
    • Real-time Hybrid Hair Rendering
    • Real-Time Procedural Generation with GPU Work Graphs
    • Scalable rendering for very large meshes
    • Spatiotemporal Variance-Guided Filtering for Motion Blur
    • Subpixel Reconstruction Antialiasing
    • Tiled light trees
    • Towards Practical Meshlet Compression
  • About
  • Archive

Pimpl your C++ code

March 14, 2009
  • Programming
approximately 7 minutes to read

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;
}
Previous post
Next post

Recent posts

  • Data formats: Why CSV and JSON aren't the best
    Posted on 2024-12-29
  • Replacing cron with systemd-timers
    Posted on 2024-04-21
  • Open Source Maintenance
    Posted on 2024-04-02
  • Angular, Caddy, Gunicorn and Django
    Posted on 2023-10-21
  • Effective meetings
    Posted on 2022-09-12
  • Older posts

Find me on the web

  • GitHub
  • GPU database
  • Projects

Follow me

Anteru NIV_Anteru
Contents © 2005-2025
Anteru
Imprint/Impressum
Privacy policy/Datenschutz
Made with Liara
Last updated February 03, 2019