Solution
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—that is, members can be freely
added or removed—without recompiling client code.
Let's answer the Item questions one at a
time.
-
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"—and privates are always
hidden.
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
|
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.
-
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—that is, implement Option 2 and, in addition,
to put inside the Pimpl any nonprivate functions that are used by
private functions.
|