⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ei36.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<UL><PRE>
pa-&gt;fly(JFK);                  // calls Airplane::fly!
</PRE>
</UL><A NAME="7082"></A>
<P><A NAME="dingp23"></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(23);</SCRIPT>
</NOBR></P>
<A NAME="7083"></A>
<P><A NAME="dingp24"></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(24);</SCRIPT>
</NOBR></P>
<A NAME="7085"></A>
<UL><PRE>class Airplane {
public:
  virtual void fly(const Airport&amp; destination) = 0;
</PRE>
</UL><A NAME="7086"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7087"></A>
<UL><PRE>protected:
  void defaultFly(const Airport&amp; destination);
};
</PRE>
</UL><A NAME="7089"></A>
<UL><PRE><A NAME="p165"></A>void Airplane::defaultFly(const Airport&amp; destination)
{
  <I>default code for flying an airplane to
  the given destination</I>
}
</PRE>
</UL><A NAME="7090"></A>
<P><A NAME="dingp25"></A>
Notice how <CODE>Airplane::fly</CODE> has been turned into a pure virtual function. That provides the interface for flying. The default implementation is also present in the <CODE>Airplane</CODE> class, but now it's in the form of an independent function, <CODE>defaultFly</CODE>. Classes like <CODE>ModelA</CODE> and <CODE>ModelB</CODE> that want to use the default behavior simply make an inline call to <CODE>defaultFly</CODE> inside their body of <CODE>fly</CODE> (but see <A HREF="./EI33_FR.HTM#6729" TARGET="_top">Item 33</A> for information on the interaction of inlining and virtual <NOBR>functions):<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<A NAME="7092"></A>
<UL><PRE>class ModelA: public Airplane {
public:
  virtual void fly(const Airport&amp; destination)
  { defaultFly(destination); }
</PRE>
</UL><A NAME="7093"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7094"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7096"></A>
<UL><PRE>class ModelB: public Airplane {
public:
  virtual void fly(const Airport&amp; destination)
  { defaultFly(destination); }
</PRE>
</UL><A NAME="7097"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7098"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7099"></A>
<P><A NAME="dingp26"></A>
For the <CODE>ModelC</CODE> class, there is no possibility of accidentally inheriting the incorrect implementation of <CODE>fly</CODE>, because the pure virtual in <CODE>Airplane</CODE> forces <CODE>ModelC</CODE> to provide its own version of <CODE>fly</CODE>.<SCRIPT>create_link(26);</SCRIPT>
</P>
<A NAME="7100"></A>
<UL><PRE>class ModelC: public Airplane {
public:
  virtual void fly(const Airport&amp; destination);<A NAME="7101"></A>
    ...
</PRE>
</UL><A NAME="7103"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7104"></A>
<UL><PRE>void ModelC::fly(const Airport&amp; destination)
{
  <I>code for flying a ModelC airplane to the given destination</I>
}
</PRE>
</UL></P><A NAME="7105"></A>
<P><A NAME="dingp27"></A>
This scheme isn't foolproof (programmers can still copy-and-paste themselves into trouble), but it's more reliable than the original design. As for <CODE>Airplane::defaultFly</CODE>, it's protected because it's truly an implementation detail of <CODE>Airplane</CODE> and its derived classes. Clients using <A NAME="p166"></A>airplanes should care only that they can be flown, not how the flying is <NOBR>implemented.<SCRIPT>create_link(27);</SCRIPT>
</NOBR></P>
<A NAME="7108"></A>
<P><A NAME="dingp28"></A>
It's also important that <CODE>Airplane::defaultFly</CODE> is a <I>nonvirtual</I> function. This is because no subclass should redefine this function, a truth to which <A HREF="./EI37_FR.HTM#7167" TARGET="_top">Item 37</A> is devoted. If <CODE>defaultFly</CODE> were virtual, you'd have a circular problem: what if some subclass forgets to redefine <CODE>defaultFly</CODE> when it's supposed <NOBR>to?<SCRIPT>create_link(28);</SCRIPT>
</NOBR></P>
<A NAME="7112"></A>
<P><A NAME="dingp29"></A>
Some people object to the idea of having separate functions for providing interface and default implementation, such as <CODE>fly</CODE> and <CODE>defaultFly</CODE> above. For one thing, they note, it pollutes the class namespace with a proliferation of closely-related function names. Yet they still agree that interface and default implementation should be separated. How do they resolve this seeming contradiction? By taking advantage of the fact that pure virtual functions must be redeclared in subclasses, but they may also have implementations of their own. Here's how the <CODE>Airplane</CODE> hierarchy could take advantage of the ability to define a pure virtual <NOBR>function:<SCRIPT>create_link(29);</SCRIPT>
</NOBR></P>
<A NAME="7115"></A>
<UL><PRE>class Airplane {
public:
  virtual void fly(const Airport&amp; destination) = 0;
</PRE>
</UL><A NAME="7116"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7117"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7119"></A>
<UL><PRE>void Airplane::fly(const Airport&amp; destination)
{
  <I>default code for flying an airplane to
  the given destination</I>
}
</PRE>
</UL><A NAME="7121"></A>
<UL><PRE>class ModelA: public Airplane {
public:
  virtual void fly(const Airport&amp; destination)
  { Airplane::fly(destination); }
</PRE>
</UL><A NAME="7122"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7123"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7125"></A>
<UL><PRE>class ModelB: public Airplane {
public:
  virtual void fly(const Airport&amp; destination)
  { Airplane::fly(destination); }
</PRE>
</UL><A NAME="7126"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7127"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7129"></A>
<UL><PRE><A NAME="p167"></A>class ModelC: public Airplane {
public:
  virtual void fly(const Airport&amp; destination);
</PRE>
</UL><A NAME="7130"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7131"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7132"></A>
<UL><PRE>void ModelC::fly(const Airport&amp; destination)
{
  <I>code for flying a ModelC airplane to the given destination</I>
}
</PRE>
</UL><A NAME="7133"></A>
<P><A NAME="dingp30"></A>
This is almost exactly the same design as before, except that the body of the pure virtual function <CODE>Airplane::fly</CODE> takes the place of the independent function <CODE>Airplane::defaultFly</CODE>. In essence, <CODE>fly</CODE> has been broken into its two fundamental components. Its declaration specifies its interface (which derived classes <I>must</I> use), while its definition specifies its default behavior (which derived classes <I>may</I> use, but only if they explicitly request it). In merging <CODE>fly</CODE> and <CODE>defaultFly</CODE>, however, you've lost the ability to give the two functions different protection levels: the code that used to be <CODE>protected</CODE> (by being in <CODE>defaultFly</CODE>) is now <CODE>public</CODE> (because it's in <CODE>fly</CODE>).<SCRIPT>create_link(30);</SCRIPT>
</P>
<A NAME="7136"></A>
<P><A NAME="dingp31"></A>
Finally, we come to <CODE>Shape</CODE>'s nonvirtual function, <CODE>objectID</CODE>. When a member function is nonvirtual, it's not supposed to behave differently in derived classes. In fact, a nonvirtual member function specifies an <I>invariant over specialization</I>, because it identifies behavior that is not supposed to change, no matter how specialized a derived class becomes. As <NOBR>such,<SCRIPT>create_link(31);</SCRIPT>
</NOBR></P><UL><A NAME="7139"></A>
<P>
<A NAME="dingp32"></A><LI>The purpose of declaring a nonvirtual function is to have derived classes inherit a function <I>interface as well as a mandatory implementation.</I><SCRIPT>create_link(32);</SCRIPT>

</UL></P>
<A NAME="7140"></A>
<P><A NAME="dingp33"></A>
You can think of the declaration for <CODE>Shape::objectID</CODE> as saying, "Every <CODE>Shape</CODE> object has a function that yields an object identifier, and that object identifier is always computed in the same way. That way is determined by the definition of <CODE>Shape::objectID</CODE>, and no derived class should try to change how it's done." Because a nonvirtual function identifies an <I>invariant</I> over specialization, it should never be redefined in a subclass, a point that is discussed in detail in <A HREF="./EI37_FR.HTM#7167" TARGET="_top">Item 37</A>.<SCRIPT>create_link(33);</SCRIPT>
</P>
<A NAME="7144"></A>
<P><A NAME="dingp34"></A>
The differences in declarations for pure virtual, simple virtual, and nonvirtual functions allow you to specify with precision what you want derived classes to inherit: interface only, interface and a default implementation, or interface and a mandatory implementation, respectively. Because these different types of declarations mean fundamentally different things, you must choose carefully among them when you de<A NAME="p168"></A>clare your member functions. If you do, you should avoid the two most common mistakes made by inexperienced class <NOBR>designers.<SCRIPT>create_link(34);</SCRIPT>
</NOBR></P>
<A NAME="7145"></A>
<P><A NAME="dingp35"></A>
The first mistake is to declare all functions nonvirtual. That leaves no room for specialization in derived classes; nonvirtual destructors are particularly problematic (see <A HREF="./EI14_FR.HTM#223029" TARGET="_top">Item 14</A>). Of course, it's perfectly reasonable to design a class that is not intended to be used as a base class. <A HREF="../MEC/MI34_FR.HTM#33950" TARGET="_top">Item M34</A> gives an example of a case where you might want to. In that case, a set of exclusively nonvirtual member functions is appropriate. Too often, however, such classes are declared either out of ignorance of the differences between virtual and nonvirtual functions or as a result of an unsubstantiated concern over the performance cost of virtual functions (see <A HREF="../MEC/MI24_FR.HTM#41284" TARGET="_top">Item M24</A>). The fact of the matter is that almost any class that's to be used as a base class will have virtual functions (again, see <A HREF="./EI14_FR.HTM#223029" TARGET="_top">Item 14</A>).<SCRIPT>create_link(35);</SCRIPT>
</P>
<A NAME="7152"></A>
<P><A NAME="dingp36"></A>
If you're concerned about the cost of virtual functions, allow me to bring up the rule of 80-20 (see <A HREF="../MEC/MI16_FR.HTM#40995" TARGET="_top">Item M16</A>), which states that in a typical program, 80 percent of the runtime will be spent executing just 20 percent of the code. This rule is important, because it means that, on average, 80 percent of your function calls can be virtual without having the slightest detectable impact on your program's overall performance. Before you go gray worrying about whether you can afford the cost of a virtual function, then, take the simple precaution of making sure that you're focusing on the 20 percent of your program where the decision might really make a <NOBR>difference.<SCRIPT>create_link(36);</SCRIPT>
</NOBR></P>
<A NAME="7157"></A>
<P><A NAME="dingp37"></A>
The other common problem is to declare <I>all</I> member functions virtual. Sometimes this is the right thing to do &#151; witness Protocol classes (see <A HREF="./EI34_FR.HTM#6793" TARGET="_top">Item 34</A>), for example. However, it can also be a sign of a class designer who lacks the backbone to take a firm stand. Some functions should <I>not</I> be redefinable in derived classes, and whenever that's the case, you've got to say so by making those functions nonvirtual. It serves no one to pretend that your class can be all things to all people if they'll just take the time to redefine all your functions. Remember that if you have a base class <CODE>B</CODE>, a derived class <CODE>D</CODE>, and a member function <CODE>mf</CODE>, then each of the following calls to <CODE>mf</CODE> <I>must</I> work <NOBR>properly:<SCRIPT>create_link(37);</SCRIPT>
</NOBR></P>
<A NAME="7158"></A>
<UL><PRE>D *pd = new D;
B *pb = pd;
</PRE>
</UL><A NAME="7159"></A>
<UL><PRE>
pb-&gt;mf();                    // call mf through a
                             // pointer-to-base
</PRE>
</UL><A NAME="7160"></A>
<UL><PRE>
pd-&gt;mf();                    // call mf through a
                             // pointer-to-derived
</PRE>
</UL><A NAME="7161"></A>
<P><A NAME="dingp38"></A>
<A NAME="p169"></A>Sometimes, you must make <CODE>mf</CODE> a nonvirtual function to ensure that everything behaves the way it's supposed to (see <A HREF="./EI37_FR.HTM#7167" TARGET="_top">Item 37</A>). If you have an invariant over specialization, don't be afraid to say <NOBR>so!<SCRIPT>create_link(38);</SCRIPT>
</NOBR></P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI35_FR.HTM" TARGET="_top">Item 35: Make sure public inheritance models "isa."</A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="./EI37_FR.HTM" TARGET="_top">Item 37: Never redefine an inherited nonvirtual function.</A></FONT></DIV>
</BODY>
</HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -