# Pimpl The **pimpl** idiom (pointer to implementation) hides a class's private data members behind a single opaque pointer to a forward-declared implementation struct. The header that clients `#include` only ever sees the pointer and a forward declaration, never the actual member types, which means changing the private implementation no longer forces every translation unit that includes the header to recompile. ```cpp // widget.h — clients only see this class Widget { public: Widget(); ~Widget(); void doThing(); private: struct Impl; std::unique_ptr impl; }; ``` ```cpp // widget.cpp — implementation detail, changes here don't touch widget.h struct Widget::Impl { int state = 0; std::vector data; }; Widget::Widget() : impl(std::make_unique()) {} Widget::~Widget() = default; void Widget::doThing() { impl->state++; } ``` ## Why the destructor has to be out of line `std::unique_ptr`'s destructor needs `Impl`'s full definition to call `delete` on it, and at the point the compiler processes `widget.h`, `Impl` is only forward-declared. If the destructor is left implicit (or defined inline in the header), the compiler tries to instantiate `unique_ptr`'s deleter before `Impl` is a complete type, and the build fails with an error about deleting an incomplete type. Declaring `~Widget()` in the header but defining it (even as `= default`) in the `.cpp` file, after `Impl` is fully defined, defers that instantiation to where it can succeed. The same reasoning forces the constructor, or anything else that touches `impl`, out of the header too, which is why pimpl classes end up with more out-of-line boilerplate than a typical class. ## What it actually buys Compile-time isolation is the main win: a header using pimpl only changes (forcing dependent recompiles) when the public interface changes, not when a private member is added or a private dependency's header changes. This matters most in large codebases where a widely-included header pulling in heavy dependencies (say, a networking library used only internally) forces a full rebuild on every change to that dependency. Pimpl also gives a clean ABI boundary: the size and layout of `Widget` as seen by client code stays stable even if `Impl` changes size, which is useful for shared libraries that need binary compatibility across versions. ## The cost Every call through a pimpl'd class adds a pointer indirection and, if `Impl` is heap-allocated (the normal case), a heap allocation per object plus a cache miss on the indirection. This is a real cost in hot paths, and the extra out-of-line boilerplate (constructor, destructor, and any special member function that would otherwise be implicitly generated) means pimpl is worth reaching for only when compile-time isolation or ABI stability actually matters, not as a default way to write classes. ## Links - https://en.cppreference.com/w/cpp/language/pimpl.html