📄 ec6.htm
字号:
<A NAME="7007"></A><A NAME="p161"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="#6914">Item 35: Make sure public inheritance models "isa."</A>
<BR>Continue to <A HREF="#7167">Item 37: Never redefine an inherited nonvirtual function.</A></FONT></DIV>
<P><A NAME="dingp47"></A><FONT ID="eititle">Item 36: Differentiate between inheritance of interface and inheritance of implementation.</FONT><SCRIPT>create_link(47);</SCRIPT>
</P>
<A NAME="7008"></A>
<P><A NAME="dingp48"></A>
The seemingly straightforward notion of (public) inheritance turns out, upon closer examination, to be composed of two separable parts: inheritance of function interfaces and inheritance of function implementations. The difference between these two kinds of inheritance corresponds exactly to the difference between function declarations and function definitions discussed in the <A HREF="./ECINTRFR.HTM" TARGET="_top">Introduction</a> to this <NOBR>book.<SCRIPT>create_link(48);</SCRIPT>
</NOBR></P>
<A NAME="7009"></A>
<P><A NAME="dingp49"></A>
As a class designer, you sometimes want derived classes to inherit only the interface (declaration) of a member function; sometimes you want derived classes to inherit both the interface and the implementation for a function, but you want to allow them to override the implementation you provide; and sometimes you want them to inherit both interface and implementation without allowing them to override <NOBR>anything.<SCRIPT>create_link(49);</SCRIPT>
</NOBR></P>
<A NAME="7010"></A>
<P><A NAME="dingp50"></A>
To get a better feel for the differences among these options, consider a class hierarchy for representing geometric shapes in a graphics <NOBR>application:<SCRIPT>create_link(50);</SCRIPT>
</NOBR></P>
<A NAME="7012"></A>
<UL><PRE>class Shape {
public:
virtual void draw() const = 0;
</PRE>
</UL><A NAME="7013"></A>
<UL><PRE> virtual void error(const string& msg);
</PRE>
</UL><A NAME="7014"></A>
<UL><PRE> int objectID() const;
</PRE>
</UL><A NAME="7015"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="7016"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7018"></A>
<UL><PRE>class Rectangle: public Shape { ... };
</PRE>
</UL><A NAME="7020"></A>
<UL><PRE>class Ellipse: public Shape { ... };
</PRE>
</UL><A NAME="7022"></A>
<P><A NAME="dingp51"></A>
<CODE>Shape</CODE> is an abstract class; its pure virtual function <CODE>draw</CODE> marks it as such. As a result, clients cannot create instances of the <CODE>Shape</CODE> class, only of the classes derived from it. Nonetheless, <CODE>Shape</CODE> exerts a strong influence on all classes that (publicly) inherit from it, <NOBR>because<SCRIPT>create_link(51);</SCRIPT>
</NOBR></P><UL><A NAME="7023"></A>
<P><A NAME="dingp52"></A><LI>Member function <I>interfaces are always inherited</I>. As explained in <A HREF="#6914">Item 35</A>, public inheritance means isa, so anything that is true of a base class must also be true of its derived classes. Hence, if a function applies to a class, it must also apply to its subclasses.<SCRIPT>create_link(52);</SCRIPT>
</UL></P>
<A NAME="7027"></A>
<P><A NAME="dingp53"></A>
Three functions are declared in the <CODE>Shape</CODE> class. The first, <CODE>draw</CODE>, draws the current object on an implicit display. The second, <CODE>error</CODE>, is called by member functions if they need to report an error. The third, <CODE>objectID</CODE>, returns a unique integer identifier for the current object; <A HREF="./EC3_FR.HTM#2264" TARGET="_top">Item 17</A> <A NAME="p162"></A>gives an example of how such a function might be used. Each function is declared in a different way: <CODE>draw</CODE> is a pure virtual function; <CODE>error</CODE> is a simple (impure?) virtual function; and <CODE>objectID</CODE> is a nonvirtual function. What are the implications of these different <NOBR>declarations?<SCRIPT>create_link(53);</SCRIPT>
</NOBR></P>
<A NAME="7031"></A>
<P><A NAME="dingp54"></A>
Consider first the pure virtual function <CODE>draw</CODE>. The two most salient features of pure virtual functions are that they <I>must</I> be redeclared by any concrete class that inherits them, and they typically have no definition in abstract classes. Put these two traits together, and you realize <NOBR>that<SCRIPT>create_link(54);</SCRIPT>
</NOBR></P>
<A NAME="7033"></A>
<P>
<UL><A NAME="dingp55"></A><LI>The purpose of declaring a pure virtual function is to have derived classes inherit a function <I>interface only</I>.<SCRIPT>create_link(55);</SCRIPT>
</UL></P>
<A NAME="7034"></A>
<P><A NAME="dingp56"></A>
This makes perfect sense for the <CODE>Shape::draw</CODE> function, because it is a reasonable demand that all <CODE>Shape</CODE> objects must be <CODE>draw</CODE>able, but the <CODE>Shape</CODE> class can provide no reasonable default implementation for that function. The algorithm for drawing an ellipse is very different from the algorithm for drawing a rectangle, for example. A good way to interpret the declaration of <CODE>Shape::draw</CODE> is as saying to designers of subclasses, "You must provide a <CODE>draw</CODE> function, but I have no idea how you're going to implement <NOBR>it."<SCRIPT>create_link(56);</SCRIPT>
</NOBR></P>
<A NAME="7036"></A>
<P><A NAME="dingp57"></A>
Incidentally, it <I>is</I> possible to provide a definition for a pure virtual function. That is, you could provide an implementation for <CODE>Shape::draw</CODE>, and C++ wouldn't complain, but the only way to call it would be to fully specify the call with the class <NOBR>name:<SCRIPT>create_link(57);</SCRIPT>
</NOBR></P>
<A NAME="7037"></A>
<UL><PRE>
Shape *ps = new Shape; // error! Shape is abstract
</PRE>
</UL><A NAME="7038"></A>
<UL><PRE>
Shape *ps1 = new Rectangle; // fine
ps1->draw(); // calls Rectangle::draw
</PRE>
</UL><A NAME="7039"></A>
<UL><PRE>
Shape *ps2 = new Ellipse; // fine
ps2->draw(); // calls Ellipse::draw
</PRE>
</UL><A NAME="7040"></A>
<UL><PRE>
ps1->Shape::draw(); // calls Shape::draw
</PRE>
</UL><A NAME="7041"></A>
<UL><PRE>
ps2->Shape::draw(); // calls Shape::draw
</PRE>
</UL></P>
<A NAME="7042"></A>
<P><A NAME="dingp58"></A>
Aside from helping impress fellow programmers at cocktail parties, knowledge of this feature is generally of limited utility. As you'll see below, however, it can be employed as a mechanism for providing a safer-than-usual default implementation for simple (impure) virtual <NOBR>functions.<SCRIPT>create_link(58);</SCRIPT>
</NOBR></P>
<A NAME="7043"></A>
<P><A NAME="dingp59"></A>
Sometimes it's useful to declare a class containing <I>nothing</I> but pure virtual functions. Such a <I>Protocol class</I> can provide only function interfaces for derived classes, never implementations. Protocol classes are described in <A HREF="./EC5_FR.HTM#6793" TARGET="_top">Item 34</A> and are mentioned again in <A HREF="#7778">Item 43</A>.<SCRIPT>create_link(59);</SCRIPT>
</P>
<A NAME="7053"></A>
<P><A NAME="dingp60"></A>
<A NAME="p163"></A>The story behind simple virtual functions is a bit different from that behind pure virtuals. As usual, derived classes inherit the interface of the function, but simple virtual functions traditionally provide an implementation that derived classes may or may not choose to override. If you think about this for a minute, you'll realize <NOBR>that<SCRIPT>create_link(60);</SCRIPT>
</NOBR></P><UL><A NAME="7054"></A>
<P>
<A NAME="dingp61"></A><LI>The purpose of declaring a simple virtual function is to have derived classes inherit a function <I>interface as well as a default implementation</I>.<SCRIPT>create_link(61);</SCRIPT>
</UL></P>
<A NAME="7055"></A>
<P><A NAME="dingp62"></A>
In the case of <CODE>Shape::error</CODE>, the interface says that every class must support a function to be called when an error is encountered, but each class is free to handle errors in whatever way it sees fit. If a class doesn't want to do anything special, it can just fall back on the default error-handling provided in the <CODE>Shape</CODE> class. That is, the declaration of <CODE>Shape::error</CODE> says to designers of subclasses, "You've got to support an <CODE>error</CODE> function, but if you don't want to write your own, you can fall back on the default version in the <CODE>Shape</CODE> <NOBR>class."<SCRIPT>create_link(62);</SCRIPT>
</NOBR></P>
<A NAME="7056"></A>
<P><A NAME="dingp63"></A>
It turns out that it can be dangerous to allow simple virtual functions to specify both a function declaration and a default implementation. To see why, consider a hierarchy of airplanes for XYZ Airlines. XYZ has only two kinds of planes, the Model A and the Model B, and both are flown in exactly the same way. Hence, XYZ designs the following <NOBR>hierarchy:<SCRIPT>create_link(63);</SCRIPT>
</NOBR></P>
<A NAME="7059"></A>
<UL><PRE>class Airport { ... }; // represents airports
</PRE>
</UL><A NAME="7061"></A>
<UL><PRE>class Airplane {
public:
virtual void fly(const Airport& destination);
</PRE>
</UL><A NAME="7062"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="7063"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7065"></A>
<UL><PRE>void Airplane::fly(const Airport& destination)
{
<I>default code for flying an airplane to
the given destination</I>
}
</PRE>
</UL><A NAME="7067"></A>
<UL><PRE>class ModelA: public Airplane { ... };
</PRE>
</UL><A NAME="7069"></A>
<UL><PRE>class ModelB: public Airplane { ... };
</PRE>
</UL><A NAME="7070"></A>
<P><A NAME="dingp64"></A>
To express that all planes have to support a <CODE>fly</CODE> function, and in recognition of the fact that different models of plane could, in principle, require different implementations for <CODE>fly</CODE>, <CODE>Airplane::fly</CODE> is declared virtual. However, in order to avoid writing identical code in the <CODE>ModelA</CODE> <A NAME="p164"></A>and <CODE>ModelB</CODE> classes, the default flying behavior is provided as the body of <CODE>Airplane::fly</CODE>, which both <CODE>ModelA</CODE> and <CODE>ModelB</CODE> <NOBR>inherit.<SCRIPT>create_link(64);</SCRIPT>
</NOBR></P>
<A NAME="19713"></A>
<P><A NAME="dingp65"></A>
This is a classic object-oriented design. Two classes share a common feature (the way they implement <CODE>fly</CODE>), so the common feature is moved into a base class, and the feature is inherited by the two classes. This design makes common features explicit, avoids code duplication, facilitates future enhancements, and eases long-term maintenance — all the things for which object-oriented technology is so highly touted. XYZ Airlines should be <NOBR>proud.<SCRIPT>create_link(65);</SCRIPT>
</NOBR></P>
<A NAME="7071"></A>
<P><A NAME="dingp66"></A>
Now suppose that XYZ, its fortunes on the rise, decides to acquire a new type of airplane, the Model C. The Model C differs from the Model A and the Model B. In particular, it is flown <NOBR>differently.<SCRIPT>create_link(66);</SCRIPT>
</NOBR></P>
<A NAME="7072"></A>
<P><A NAME="dingp67"></A>
XYZ's programmers add the class for Model C to the hierarchy, but in their haste to get the new model into service, they forget to redefine the <CODE>fly</CODE> <NOBR>function:<SCRIPT>create_link(67);</SCRIPT>
</NOBR></P>
<A NAME="7074"></A>
<UL><PRE>class ModelC: public Airplane {
</PRE>
</UL><A NAME="7075"></A>
<UL><PRE>
... // no fly function is
// declared
};
</PRE>
</UL><A NAME="7077"></A>
<A NAME="dingp68"></A>In their code, then, they have something akin to the <NOBR>following:<SCRIPT>create_link(68);</SCRIPT>
</NOBR></P>
<A NAME="7078"></A>
<UL><PRE>
Airport JFK(...); // JFK is an airport in
// New York City
</PRE>
</UL><A NAME="7079"></A>
<UL><PRE>Airplane *pa = new ModelC;
</PRE>
</UL><A NAME="7080"></A>
<UL><PRE>...
</PRE>
</UL><A NAME="7081"></A>
<UL><PRE>
pa->fly(JFK); // calls Airplane::fly!
</PRE>
</UL><A NAME="7082"></A>
<P><A NAME="dingp69"></A>
This is a disaster: an attempt is being made to fly a <CODE>ModelC</CODE> object as if it were a <CODE>ModelA</CODE> or a <CODE>ModelB</CODE>. That's not the kind of behavior that inspires confidence in the traveling <NOBR>public.<SCRIPT>create_link(69);</SCRIPT>
</NOBR></P>
<A NAME="7083"></A>
<P><A NAME="dingp70"></A>
The problem here is not that <CODE>Airplane::fly</CODE> has default behavior, but that <CODE>ModelC</CODE> was allowed to inherit that behavior without explicitly saying that it wanted to. Fortunately, it's easy to offer default behavior to subclasses, but not give it to them unless they ask for it. The trick is to sever the connection between the <i>interface</i> of the virtual function and its default <I>implementation</I>. Here's one way to do <NOBR>it:<SCRIPT>create_link(70);</SCRIPT>
</NOBR></P>
<A NAME="7085"></A>
<UL><PRE>class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
</PRE>
</UL><A NAME="7086"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="7087"></A>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -