📄 chapter15.html
字号:
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The solution is called <I>late
binding<A NAME="Index2410"></A><A NAME="Index2411"></A></I>, which means the
binding occurs at runtime, based on the type of the object. Late binding is also
called <I>dynamic binding<A NAME="Index2412"></A><A NAME="Index2413"></A></I> or
<I>runtime binding<A NAME="Index2414"></A><A NAME="Index2415"></A></I>. When a
language implements late binding, there must be some mechanism to determine the
type of the object at runtime and call the appropriate member function. In the
case of a compiled language, the compiler still doesn’t know the actual
object type, but it inserts code that finds out and calls the correct function
body. The late-binding mechanism varies from language to language, but you can
imagine that some sort of type information must be installed in the objects.
You’ll see how this works
later.</FONT><A NAME="_Toc305593265"></A><A NAME="_Toc305628737"></A><A NAME="_Toc312374043"></A><A NAME="_Toc472655020"></A><BR></P></DIV>
<A NAME="Heading438"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
virtual functions</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To cause late binding to occur for a
particular function, C++ requires that you use the <B>virtual</B>
keyword<A NAME="Index2416"></A><A NAME="Index2417"></A> when declaring the
function in the base class. Late binding occurs only with <B>virtual</B>
functions, and only when you’re using an address of the base class where
those <B>virtual</B> functions exist, although they may also be defined in an
earlier base class.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To create a member function as
<B>virtual</B>, you simply precede the declaration <A NAME="Index2418"></A>of
the function with the keyword <B>virtual</B>. Only the declaration needs the
<B>virtual</B> keyword, not the definition. If a function is declared as
<B>virtual</B> in the base class, it is <B>virtual</B> in all the derived
classes. The redefinition of a <B>virtual </B>function in a derived class is
usually called
<I>overriding<A NAME="Index2419"></A><A NAME="Index2420"></A><A NAME="Index2421"></A>.</I></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><A NAME="Index2422"></A><A NAME="Index2423"></A><A NAME="Index2424"></A><FONT FACE="Georgia">Notice
that you are only required to declare a function <B>virtual</B> in the base
class. All derived-class functions that match the signature of the base-class
declaration will be called using the virtual mechanism. You <I>can</I> use the
<B>virtual</B> keyword in the derived-class declarations
<A NAME="Index2425"></A><A NAME="Index2426"></A><A NAME="Index2427"></A>(it does
no harm to do so), but it is redundant and can be confusing. </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To get the desired behavior from
<B>Instrument2.cpp</B>, simply add the <B>virtual</B> keyword in the base class
before <B>play( )</B>:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C15:Instrument3.cpp</font>
<font color=#009900>// Late binding with the virtual keyword</font>
#include <iostream>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>enum</font> note { middleC, Csharp, Cflat }; <font color=#009900>// Etc.</font>
<font color=#0000ff>class</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#0000ff>virtual</font> <font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Instrument::play"</font> << endl;
}
};
<font color=#009900>// Wind objects are Instruments</font>
<font color=#009900>// because they have the same interface:</font>
<font color=#0000ff>class</font> Wind : <font color=#0000ff>public</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#009900>// Override interface function:</font>
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Wind::play"</font> << endl;
}
};
<font color=#0000ff>void</font> tune(Instrument& i) {
<font color=#009900>// ...</font>
i.play(middleC);
}
<font color=#0000ff>int</font> main() {
Wind flute;
tune(flute); <font color=#009900>// Upcasting</font>
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This file is identical to
<B>Instrument2.cpp</B> except for the addition of the <B>virtual</B> keyword,
and yet the behavior is significantly different: Now the output is
<B>Wind::play</B>.</FONT><A NAME="_Toc312374044"></A><A NAME="_Toc472655021"></A><BR></P></DIV>
<A NAME="Heading439"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Extensibility<BR><A NAME="Index2428"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">With <B>play( )</B> defined as
<B>virtual</B> in the base class, you can add as many new types as you want
without changing the <B>tune( )</B> function. In a well-designed OOP
program, most or all of your functions will follow the model of
<B>tune( )</B> and communicate only with the base-class
interface<A NAME="Index2429"></A><A NAME="Index2430"></A>. Such a program is
<I>extensible<A NAME="Index2431"></A></I> because you can add new functionality
by inheriting new data types from the common base class. The functions that
manipulate the base-class interface will not need to be changed at all to
accommodate the new classes.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here’s the instrument example with
more virtual functions and a number of new classes, all of which work correctly
with the old, unchanged <B>tune( )</B> function:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C15:Instrument4.cpp</font>
<font color=#009900>// Extensibility in OOP</font>
#include <iostream>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>enum</font> note { middleC, Csharp, Cflat }; <font color=#009900>// Etc.</font>
<font color=#0000ff>class</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#0000ff>virtual</font> <font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Instrument::play"</font> << endl;
}
<font color=#0000ff>virtual</font> <font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> {
<font color=#0000ff>return</font> <font color=#004488>"Instrument"</font>;
}
<font color=#009900>// Assume this will modify the object:</font>
<font color=#0000ff>virtual</font> <font color=#0000ff>void</font> adjust(<font color=#0000ff>int</font>) {}
};
<font color=#0000ff>class</font> Wind : <font color=#0000ff>public</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Wind::play"</font> << endl;
}
<font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Wind"</font>; }
<font color=#0000ff>void</font> adjust(<font color=#0000ff>int</font>) {}
};
<font color=#0000ff>class</font> Percussion : <font color=#0000ff>public</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Percussion::play"</font> << endl;
}
<font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Percussion"</font>; }
<font color=#0000ff>void</font> adjust(<font color=#0000ff>int</font>) {}
};
<font color=#0000ff>class</font> Stringed : <font color=#0000ff>public</font> Instrument {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Stringed::play"</font> << endl;
}
<font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Stringed"</font>; }
<font color=#0000ff>void</font> adjust(<font color=#0000ff>int</font>) {}
};
<font color=#0000ff>class</font> Brass : <font color=#0000ff>public</font> Wind {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Brass::play"</font> << endl;
}
<font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Brass"</font>; }
};
<font color=#0000ff>class</font> Woodwind : <font color=#0000ff>public</font> Wind {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> play(note) <font color=#0000ff>const</font> {
cout << <font color=#004488>"Woodwind::play"</font> << endl;
}
<font color=#0000ff>char</font>* what() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Woodwind"</font>; }
};
<font color=#009900>// Identical function from before:</font>
<font color=#0000ff>void</font> tune(Instrument& i) {
<font color=#009900>// ...</font>
i.play(middleC);
}
<font color=#009900>// New function:</font>
<font color=#0000ff>void</font> f(Instrument& i) { i.adjust(1); }
<font color=#009900>// Upcasting during array initialization:</font>
Instrument* A[] = {
<font color=#0000ff>new</font> Wind,
<font color=#0000ff>new</font> Percussion,
<font color=#0000ff>new</font> Stringed,
<font color=#0000ff>new</font> Brass,
};
<font color=#0000ff>int</font> main() {
Wind flute;
Percussion drum;
Stringed violin;
Brass flugelhorn;
Woodwind recorder;
tune(flute);
tune(drum);
tune(violin);
tune(flugelhorn);
tune(recorder);
f(flugelhorn);
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You can see that another inheritance
level has been added beneath <B>Wind</B>, but the <B>virtual</B> mechanism works
correctly no matter how many levels there are. The <B>adjust( )</B>
function is <I>not</I> overridden for <B>Brass</B> and <B>Woodwind</B>. When
this happens, the “closest” definition in the inheritance hierarchy
is automatically used – the compiler guarantees there’s always
<I>some</I> definition for a virtual function, so you’ll never end up with
a call that doesn’t bind to a function body. (That would be
disastrous.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The array <B>A[ ]</B> contains pointers
to the base class <B>Instrument</B>, so upcasting occurs during the process of
array initialization. This array and the function <B>f( )</B> will be used
in later discussions.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the call to <B>tune( )</B>,
upcasting <A NAME="Index2432"></A>is performed on each different type of object,
yet the desired behavior always takes place. This can be described as
“sending a message<A NAME="Index2433"></A><A NAME="Index2434"></A> to an
object and letting the object worry about what to do with it.” The
<B>virtual</B> function is the lens to use when you’re trying to analyze a
project: Where should the base classes occur, and how might you want to extend
the program? However, even if you don’t discover the proper base class
interfaces and virtual functions at the initial creation of the program,
you’ll often discover them later, even much later, when you set out to
extend or otherwise maintain the program. This is not an analysis or design
error; it simply means you didn’t or couldn’t know all the
information the first time. Because of the tight class modularization in C++, it
isn’t a large problem when this occurs because changes you make in one
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -