I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Class X uses a variant of the handle/body idiom. As documented by Coplien (Coplien92), handle/body was described as primarily useful for reference counting of a shared implementation, but it also has more-general implementation-hiding uses. For convenience, from now on I'll call X the "visible class" and XImpl the "Pimpl class."

One big advantage of this idiom is that it breaks compile-time dependencies. First, system builds run faster, because using a Pimpl can eliminate extra #includes as demonstrated in Items 27 and 28. I have worked on projects in which converting just a few widely-visible classes to use Pimpls halved the system's build time. Second, it localizes the build impact of code changes because the parts of a class that reside in the Pimpl can be freely changed梩hat is, members can be freely added or removed梬ithout recompiling client code.

Let's answer the Item questions one at a time.

  1. What should go into XImpl?

    Option 1 (Score: 6 / 10): Put all private data (but not functions) into XImpl. This is a good start, because it hides any class that appeared only as a data member. Still, we can usually do better.

    Option 2 (Score: 10 / 10): Put all nonvirtual private members into XImpl. This is (almost) my usual practice these days. After all, in C++, the phrase "client code shouldn't and doesn't care about these parts" is spelled "private"梐nd privates are always hidden.[4]

    [4] Except in some liberal European countries.

    There are some caveats.

    • You can't hide virtual member functions in the Pimpl, even if the virtual functions are private. If the virtual function overrides one inherited from a base class, then it must appear in the actual derived class. If the virtual function is not inherited, then it must still appear in the visible class in order to be available for overriding by further derived classes.

      Virtual functions should normally be private, except that they have to be protected if a derived class's version needs to call the base class's version (for example, for a virtual DoWrite() persistence function).

    • Functions in the Pimpl may require a "back pointer" to the visible object if they need to, in turn, use visible functions, which adds another level of indirection. (By convention, such a back pointer is usually named self_ at PeerDirect.)

    • Often the best compromise is to use Option 2, and in addition to put into XImpl only those non-private functions that need to be called by the private ones (see the back pointer comments below).

      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 data and member functions).


      Option 3 (Score: 0 / 10): Put all private and protected members into XImpl. Taking this extra step to include protected members is actually wrong. Protected members should never go into a Pimpl, because putting them there just emasculates them. After all, protected members exist specifically to be seen and used by derived classes, so they aren't nearly as useful if derived classes can't see or use them.

      Option 4 (Score: 10 / 10 in restricted cases): 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 (another handle/body variant). This is useful in a few restricted cases and has the benefit of avoiding a back pointer, because all services are available within the Pimpl class. The chief drawback is that it usually makes the visible class useless for any inheritance as either a base or a derived class.

  2. Does XImpl require a pointer back to the X object?

    Does the Pimpl require a back pointer to the visible object? The answer is: Sometimes, unhappily, yes. After all, what we're doing is (somewhat artificially) splitting each object into two halves for the purposes of hiding one part.

    Consider: Whenever a function in the visible class is called, usually some function or data in the hidden half is needed to complete the request. That's fine and reasonable. What's perhaps not as obvious at first is that often a function in the Pimpl must call a function in the visible class, usually because the called function is public or virtual. One way to minimize this is to use Option 4 (above) judiciously for the functions concerned梩hat is, implement Option 2 and, in addition, to put inside the Pimpl any nonprivate functions that are used by private functions.

    I l@ve RuBoard Previous Section Next Section