I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

There are a couple of things we weren't able to do in the previous problem.

  • We had to leave a.h and b.h. We couldn't get rid of these because X inherits from both A and B, and you always have to have full definitions for base classes so that the compiler can determine X's object size, virtual functions, and other fundamentals. (Can you anticipate how to remove one of these? Think about it: Which one can you remove, and why/how? The answer will come shortly.)

  • We had to leave list, c.h, and d.h. We couldn't get rid of these right away because a list<C> and a D appear as private data members of X. Although C appears as neither a base class nor a member, it is being used to instantiate the list member, and most current compilers require that when you instantiate list<C>, you are able to see the definition of C. (The standard doesn't require a definition here, though, so even if the compiler you are currently using has this restriction, you can expect the restriction to go away over time.)

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).

[1] I always used to write impl_. The eponymous pimpl_ was actually coined several years ago by Jeff Sumner (chief programmer at PeerDirect), due in equal parts to a penchant for Hungarian-style "p" prefixes for pointer variables and an occasional taste for horrid puns.

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"梩he opaque pointer, or Pimpl.

The major advantages of this idiom come from the fact that it breaks compile-time dependencies.

  • Types mentioned only in a class's implementation need no longer be defined for client code, which can eliminate extra #includes and improve compile speeds.

  • A class's implementation can be changed梩hat is, private members can be freely added or removed, without recompiling client code.

The major costs of this idiom are in performance.

  • Each construction/destruction must allocate/deallocate memory.

  • Each access of a hidden member can require at least one extra indirection. (If the hidden member being accessed itself uses a back pointer to call a function in the visible class, there will be multiple indirections.)

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

graphics/guideline_icon.gif

For widely used classes, prefer to use the compiler-firewall idiom (Pimpl Idiom) to hide implementation details. Use an opaque pointer (a pointer to a declared, but undefined, class) declared as "struct XxxxImpl; XxxxImpl* pimpl_;" to store private members (including both state variables and member functions). For example: "class Map {private: struct MapImpl; MapImpl* pimpl_; };".


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 Previous Section Next Section