cpp programming codePackaging

PIMPL (Pointer to Implementation)

Refers to “Pointer to Implementation”, and is mostly used to clean up headers. This pattern helps with:

  • Compilation firewall: Changes to implementation don’t require recompiling files that include the header
  • Hide implementation details: Keep private members truly private
  • Reduce header dependencies: Forward declarations instead of includes

When to use generally whenever you are using external, large header-only libraries. To stop people downstream from depending on such a large header during compilation.

Can also be used to solve circular references. Though, generally the fact that you even ran into a circular dependency issue hints at a bigger design problem.

If you need a complete type from the library, then thats when you gots to put it in the header.

Related: unique_ptr usage | Encapsulation

// ---- Header (would normally live in .hpp) ----
class Impl; // forward declaration of implementation
 
class Image {
public:
    Image();
    explicit Image(int w, int h);
    ~Image();                    // out-of-line to see full Impl
 
    void set_pixel(int x, int y, uint8_t v);
    uint8_t get_pixel(int x, int y) const;
    void info() const;
 
private:
    std::unique_ptr<Impl> p_;    // opaque pointer
};
 
// ---- Implementation (would normally live in .cpp) ----
class Image::Impl {
public:
    int w{0}, h{0};
    std::vector<uint8_t> data; // flat grayscale
 
    Impl() = default;
    Impl(int W, int H) : w(W), h(H), data(static_cast<size_t>(W*H), 0) {}
 
    size_t idx(int x, int y) const { return static_cast<size_t>(y*w + x); }
};
 
// OR IF ITS AN EXTERNAL LIBRARY, YOU JUST #INCLUDE that library in the .cpp
 
Image::Image() : p_(std::make_unique<Impl>()) {}
Image::Image(int w, int h) : p_(std::make_unique<Impl>(w,h)) {}
Image::~Image() = default; // needs full Impl in this TU
 
void Image::set_pixel(int x, int y, uint8_t v) {
    if (x<0 || y<0 || x>=p_->w || y>=p_->h) throw std::out_of_range("pixel");
    p_->data[p_->idx(x,y)] = v;
}
uint8_t Image::get_pixel(int x, int y) const {
    if (x<0 || y<0 || x>=p_->w || y>=p_->h) throw std::out_of_range("pixel");
    return p_->data[p_->idx(x,y)];
}
void Image::info() const { std::cout << "Image " << p_->w << "x" << p_->h << "\n"; }