In C++ when anything in a header file changes, all code that includes the header (either directly or indirectly) must be recompiled. To minimalize this we use PIMPL idiom:
// file x.h class X { public: // public members protected: // protected members private: // pointer to forward declared class class XImpl *pimpl_; // opaque pointer };
Questions: -- What should go into XImpl? Options are:
Put all private data but not functions into XImpl.: Not too bad, but there is better
Put all private members into XImpl.
Put all private and protected members into XImpl. Bad, protected members must be in X
Make XImpl entirely the class that X would have been, and write X as only the public interface made up entirely of simple forwarding functions (handle/body variant).
-- Does XImpl require a pointer back to the X object?
Caveats:
You can't hide virtual member functions in the Pimpl (here private inheritance differs from membership)
Functions in Pimpl may require a "back pointer" to the visible object (by convention that is called: self_.
Often the best compromise is to use Option 2, and in addition to put into XImpl only rhose non-private functions that need to be called by private ones.
4th is better over 2nd not needed "back pointer", but X is useless for inheritance.
PIPML has some drawbacks, like allocating/deallocating objects in the heap, which could be slow.
What about this "optimalization"?
// file: y.h class Y { //... static const size_t sizeofx = /* ... */; char x_[sizeofx]; }; // file: y.cpp #include "x.h" Y::Y() { assert( sizeofx >= sizeof(X) ); new(&x_[0]) X; } Y::~Y() { (reinterpret_cast<X*>(&x_[0]))->~X(); }
Questions:
What is the Pimpl space overhead?
What is the performance overhead?
Space overhead:
#include <iostream> using namespace std; struct X { char c; struct XImpl *pimpl_; }; struct XImpl { char c; }; int main() { cout << sizeof(XImpl) << '\t' << sizeof(X) << endl; return 0; } // result: 1 8
Runtime overhead:
allocation/deallocation cost: relativelly expensive
indirect access of private members (+ back pointer)
alignment problems: new guaranties, that object will align properly, char[] buffers doesn't!
X must not use the default assignmentoperator=()
If sizeof(XImpl) grows greater then sizeofx, we need to update the source.
// file x.h class X { //... struct XImpl *pimpl_; }; // file x.cpp #include "x.h" struct XImpl { // private stuff here ... static void *operator new(size_t) { /*...*/ } static void *operator delete(void*) { /*...*/ } }; X::X() : pimpl_( new XImpl ) { } X::~X() { delete pimpl_; pimpl_ = 0; }