I l@ve RuBoard | ![]() ![]() |
Solution![]() There are a couple of things we weren't able to do in the previous problem.
Now let's talk about the beauty of Pimpls. (Yes, really.) C++ lets us easily encapsulate the private parts of a class from unauthorized access. Unfortunately, because of the header file approach inherited from C, it can take a little more work to encapsulate dependencies on a class's privates. "But," you say, "the whole point of encapsulation is that the client code shouldn't have to know or care about a class's private implementation details, right?" Right, and in C++, the client code doesn't need to know or care about access to a class's privates (because unless it's a friend, it isn't allowed any), but because the privates are visible in the header, the client code does have to depend upon any types they mention. How can we better insulate clients from a class's private implementation details? One good way is to use a special form of the handle/body idiom (Coplien92) (what I call the Pimpl Idiom because of the intentionally pronounceable pimpl_ pointer[1]) as a compilation firewall (Lakos96, Meyers98, Meyers99, Murray93).
A Pimpl is just an opaque pointer (a pointer to a forward-declared, but undefined, helper class) used to hide the private members of a class. That is, instead of writing: // file x.h class X { // public and protected members private: // private members; whenever these change, // all client code must be recompiled }; we write: // file x.h class X { // public and protected members private: struct XImpl; XImpl* pimpl_; // a pointer to a forward-declared class }; // file x.cpp struct X::XImpl { // private members; fully hidden, can be // changed at will without recompiling clients }; Every X object dynamically allocates its XImpl object. If you think of an object as a physical block, we've essentially lopped off a large chunk of the block and in its place left only "a little bump on the side"—the opaque pointer, or Pimpl. The major advantages of this idiom come from the fact that it breaks compile-time dependencies.
The major costs of this idiom are in performance.
We'll come back to these and other Pimpl issues later in this section. For now, in our example, there were three headers whose definitions were needed simply because they appeared as private members of X. If we instead restructure X to use a Pimpl, we can immediately make several further simplifications. Use the Pimpl idiom to hide the private implementation details of X. #include <list> #include "c.h" // class C #include "d.h" // class D One of these headers (c.h) can be replaced with a forward declaration, because C is still being mentioned elsewhere as a parameter or return type, and the other two (list and d.h) can disappear completely. Guideline
After making that additional change, the header looks like this: // x.h: after converting to use a Pimpl // #include <iosfwd> #include "a.h" // class A (has virtual functions) #include "b.h" // class B (has no virtual functions) class C; class E; class X : public A, private B { public: X( const C& ); B f( int, char* ); C f( int, C ); C& g( B ); E h( E ); virtual std::ostream& print( std::ostream& ) const; private: struct XImpl; XImpl* pimpl_; // opaque pointer to forward-declared class }; inline std::ostream& operator<<( std::ostream& os, const X& x ) { return x.print(os); } The private details go into X's implementation file, where client code never sees them and therefore never depends upon them. // Implementation file x.cpp // struct X::XImpl { std::list<C> clist_; D d_; }; That brings us down to including only three headers, which is a great improvement. But it turns out that there is still a little more we could do, if only we were allowed to change the structure of X more extensively. This leads us nicely into Item 28… ![]() |
I l@ve RuBoard | ![]() ![]() |