Alternative names: compilation firewall, opaque pointer, d-pointer (as in data).
Pack everything that is private into a forward declared class.
This is a variant of the Handle/Body
idiom:
A group of DesignPatterns, in which the implementation of something (the “body”) is separated from its interface by means of a level of indirection (the “handle”). Client code operates on the handle, which then operates on the body; either by simply forwarding/delegating requests or by adapting a protocol.
Problem: headers serve as interfaces, but also a declaration site for potential implementation details.
Methods must be declared inside the class. Classes without methods are useless, so class definitions usually appear in header files. This means private data and private methods are also visible in the header.
Related problem: virtual functions can be used as interfaces but this is a runtime object oriented concept. There is a tension between this and generic programming. As an example we can’t have function templates as virtual functions. See Type Erasure and Static Interfaces.
Solution: Pack everything that is private into a forward declared class. The compiler can work without having a full type declaration, only the size of the pointer is needed. There is no need to recompile the unit if the implementation changes.
For instance, Nokia’s “Qt Development Frameworks” department (formerly Trolltech) has carried out profound changes to the widget rendering at least twice during the development of their “Qt 4” class library without the need to so much as relink programs using Qt 4. https://wiki.qt.io/D-Pointer
Header:
class Parser {
public:
Parser(const char *params);
~Parser();
void parse(const char *input);
// Other methods related to responsibility of the class
...
private:
Parser(const Parser&); // noncopyable
Parser& operator=(const Parser&); //
class Impl; // Forward declaration of the implementation class
Impl *impl_; // PIMPL
};
Source:
// Include all headers the implementation requires
// The actual implementation definition:
class Parser::Impl {
public:
Impl(const char *params) {
// Actual initialization
...
}
void parse(const char *input) {
// Actual work
...
}
};
// Create an implementation object in ctor
Parser::Parser(const char *params)
: impl_(new Impl(params))
{}
// Delete the implementation in dtor
Parser::~Parser() { delete impl_; }
// Forward an operation to the implementation
void Parser::parse(const char *input) {
impl_->parse(input);
}
// Forward other operations to the implementation
...
Pros:
Cons:
const
changeIn C/C++ const
is shallow.
SomeThing & Class::someThing() const {
return d->someThing;
}
this
is const Class*
and
d
is Class::Private * const
Solution: overload operator*()
and
operator->()
and forward constness to
*d
Solution 2: access d
through an overloaded
function which returns const
if needed.
Using std::unique_ptr
pointer seems like a good
choice to avoid using raw pointers and also expresses the intent
that the implementation is exclusively owned by the pointer.
But forward declaration and std::unique_ptr
doesn’t play well. We need to define the implementation before
generating the default special members, because
unique_ptr
’s destructor requires a complete type, so
we need to define the destructor in the implementation file.
This prevents the compiler from generating default move constructor and move assignement. If we need a copyable class we need even more boilerplate.
And this violates rule of zero. Potential workaround.
Often a function in the pimpl must call a function in the visible class, usually because the called function is public or virtual.
Custom allocator, aligned_storage
. See Fast
Pimpl
Only private data
This a good start, we can get rid of the includes needed for the implementation.
Only the private methods
And leave some private members. See Priv. Less boilerplate, more speed.
All private members
Client code shouldn’t care about implementation. Full isolation.