📄 chapter15.html
字号:
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">fetches the word that <B>si</B> (that is,
<B>this</B>)<B> </B>points to, which is the VPTR. It places the VPTR into the
register <B>bx</B>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The VPTR contained in <B>bx</B> points to
the starting address of the VTABLE, but the function pointer to call isn’t
at location zero of the VTABLE, but instead at location two (because it’s
the third function in the list). For this memory model each function pointer is
two bytes long, so the compiler adds four to the VPTR to calculate where the
address of the proper function is. Note that this is a constant value,
established at compile time, so the only thing that matters is that the function
pointer at location number two is the one for <B>adjust( )</B>.
Fortunately, the compiler takes care of all the bookkeeping for you and ensures
that all the function pointers in all the VTABLEs of a particular class
hierarchy occur in the same order, regardless of the order that you may override
them in derived classes.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Once the address of the proper function
pointer in the VTABLE is calculated, that function is called. So the address is
fetched and called all at once in the statement</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>call word ptr [bx+4]</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Finally, the stack pointer is moved back
up to clean off the arguments that were pushed before the call. In C and C++
assembly code you’ll often see the caller clean off the arguments but this
may vary depending on processors and compiler
implementations.</FONT><A NAME="_Toc312374049"></A><A NAME="_Toc472655026"></A><BR></P></DIV>
<A NAME="Heading444"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Installing the vpointer<BR><A NAME="Index2463"></A><A NAME="Index2464"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because the VPTR determines the virtual
function behavior of the object, you can see how it’s critical that the
VPTR always be pointing to the proper VTABLE. You don’t ever want to be
able to make a call to a virtual function before the VPTR is properly
initialized. Of course, the place where initialization can be guaranteed is in
the constructor, but none of the <B>Instrument</B> examples has a
constructor.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This is where creation of the default
constructor is essential. In the <B>Instrument</B> examples, the compiler
creates a default constructor that does nothing except initialize the VPTR. This
constructor, of course, is automatically called for all <B>Instrument</B>
objects before you can do anything with them, so you know that it’s always
safe to call virtual functions.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The implications of the automatic
initialization of the VPTR inside the constructor are discussed in a later
section.</FONT><A NAME="_Toc312374050"></A><A NAME="_Toc472655027"></A><BR></P></DIV>
<A NAME="Heading445"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Objects are different</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It’s important to realize that
upcasting <A NAME="Index2465"></A>deals only with addresses. If the compiler has
an object, it knows the exact type and therefore (in C++) will not use late
binding for any function calls – or at least, the compiler doesn’t
<I>need</I> to use late binding. For efficiency’s sake, most compilers
will perform early binding<A NAME="Index2466"></A><A NAME="Index2467"></A> when
they are making a call to a virtual function for an object because they know the
exact type. Here’s an example:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C15:Early.cpp</font>
<font color=#009900>// Early binding & virtual functions</font>
#include <iostream>
#include <string>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>class</font> Pet {
<font color=#0000ff>public</font>:
<font color=#0000ff>virtual</font> string speak() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>""</font>; }
};
<font color=#0000ff>class</font> Dog : <font color=#0000ff>public</font> Pet {
<font color=#0000ff>public</font>:
string speak() <font color=#0000ff>const</font> { <font color=#0000ff>return</font> <font color=#004488>"Bark!"</font>; }
};
<font color=#0000ff>int</font> main() {
Dog ralph;
Pet* p1 = &ralph;
Pet& p2 = ralph;
Pet p3;
<font color=#009900>// Late binding for both:</font>
cout << <font color=#004488>"p1->speak() = "</font> << p1->speak() <<endl;
cout << <font color=#004488>"p2.speak() = "</font> << p2.speak() << endl;
<font color=#009900>// Early binding (probably):</font>
cout << <font color=#004488>"p3.speak() = "</font> << p3.speak() << endl;
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In <B>p1–>speak( )</B> and
<B>p2.speak( )</B>, addresses are used, which means the information is
incomplete: <B>p1 </B>and <B>p2 </B>can represent the address of a <B>Pet</B>
<I>or</I> something derived from <B>Pet</B>, so the virtual mechanism must be
used. When calling <B>p3.speak( )</B> there’s no ambiguity. The
compiler knows the exact type and that it’s an object, so it can’t
possibly be an object derived from <B>Pet</B> – it’s <I>exactly</I>
a <B>Pet</B>. Thus, early binding is probably used. However, if the compiler
doesn’t want to work so hard, it can still use late binding and the same
behavior will
occur.</FONT><A NAME="_Toc305593267"></A><A NAME="_Toc305628739"></A><A NAME="_Toc312374051"></A><A NAME="_Toc472655028"></A><BR></P></DIV>
<A NAME="Heading446"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Why virtual functions?</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">At this point you may have a question:
“If this technique is so important, and if it makes the
‘right’ function call all the time, why is it an option? Why do I
even need to know about it?”</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This is a good question, and the answer
is part of the fundamental philosophy of C++: “Because it’s not
quite as efficient.”<A NAME="Index2468"></A><A NAME="Index2469"></A> You
can see from the previous assembly-language output that instead of one simple
CALL to an absolute address, there are two – more sophisticated –
assembly instructions required to set up the virtual function call. This
requires both code space and execution time.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Some object-oriented languages have taken
the approach that late binding is so intrinsic to object-oriented programming
that it should always take place, that it should not be an option, and the user
shouldn’t have to know about it. This is a design decision when creating a
language, and that particular path is appropriate for many
languages.</FONT><A NAME="fnB56" HREF="#fn56">[56]</A><A NAME="Index2470"></A><A NAME="Index2471"></A><A NAME="Index2472"></A><FONT FACE="Georgia">
However, C++ comes from the C heritage, where efficiency is critical. After all,
C was created to replace assembly language for the implementation of an
operating system (thereby rendering that operating system – Unix –
far more portable than its predecessors). One of the main reasons for the
invention of C++ was to make C programmers more
efficient.</FONT><A NAME="fnB57" HREF="#fn57">[57]</A><FONT FACE="Georgia"> And
the first question asked when C programmers encounter C++ is, “What kind
of size and speed impact will I get?” If the answer were,
“Everything’s great except for function calls when you’ll
always have a little extra overhead,” many people would stick with C
rather than make the change to C++. In addition, inline functions
<A NAME="Index2473"></A><A NAME="Index2474"></A>would not be possible, because
virtual functions must have an address to put into the VTABLE. So the virtual
function is an option, <I>and</I> the language defaults to nonvirtual, which is
the fastest configuration. Stroustrup stated that his guideline was, “If
you don’t use it, you don’t pay for it.”</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Thus, the
<A NAME="Index2475"></A><A NAME="Index2476"></A><B>virtual </B>keyword is
provided for efficiency tuning. When designing your classes, however, you
shouldn’t be worrying about efficiency tuning. If you’re going to
use polymorphism, use virtual functions everywhere. You only need to look for
functions that can be made non-virtual when searching for ways to speed up your
code (and there are usually much bigger gains to be had in other areas – a
good profiler will do a better job of finding bottlenecks than you will by
making guesses).</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Anecdotal evidence suggests that the size
and speed impacts of going to C++ are within 10 percent of the size and speed of
C, and often much closer to the same. The reason you might get better size and
speed efficiency is because you may design a C++ program in a smaller, faster
way than you would using
C.</FONT><A NAME="_Toc305593268"></A><A NAME="_Toc305628740"></A><A NAME="_Toc312374052"></A><A NAME="_Toc472655029"></A><BR></P></DIV>
<A NAME="Heading447"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Abstract base classes and pure virtual
functions<BR><A NAME="Index2477"></A><A NAME="Index2478"></A><A NAME="Index2479"></A><A NAME="Index2480"></A><A NAME="Index2481"></A><A NAME="Index2482"></A></H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Often in a design, you want the base
class to present <I>only</I> an interface for its derived classes. That is, you
don’t want anyone to actually create an object of the base class, only to
upcast to it so that its interface can be used. This is accomplished by making
that class <I>abstract</I>, which happens if you give it at least one <I>pure
virtual function</I>. You can recognize a pure virtual function because it uses
the <B>virtual</B> keyword and is followed by <B>= 0</B>. If anyone tries to
make an object of an abstract class, the compiler prevents them. This is a tool
that allows you to enforce a particular design.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When an abstract class is inherited, all
pure virtual functions must be implemented, or the inherited class becomes
abstract as well. Creating a pure virtual function allows you to put a member
function in an interface without being forced to provide a possibly meaningless
body of code for that member function. At the same time, a pure virtual function
forces inherited classes to provide a definition for it. </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In all of the instrument examples, the
functions in the base class <B>Instrument</B> were always “dummy”
functions. If these functions are ever called, something is wrong. That’s
because the intent of <B>Instrument</B> is to create a common interface for all
of the classes derived from it.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo18.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The only reason to establish the common
interface<A NAME="Index2483"></A><A NAME="Index2484"></A> is so it can be
expressed differently for each different subtype. It creates a basic form that
determines what’s in common with all of the derived classes –
nothing else. So <B>Instrument</B> is an appropriate candidate to be an abstract
class. You create an abstract class when you only want to manipulate a set of
classes through a common interface, but the common interface doesn’t need
to have an implementation (or at least, a full implementation).
</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you have a concept like <B>Instrument
</B>that works as an abstract class, objects of that class almost always have no
meaning. That is, <B>Instrument</B> is meant to express only the interface, and
not a particular implementation, so creating an object that is only an
<B>Instrument</B> makes no sense, and you’ll probably want to prevent the
user from doing it. This can be accomplished by making all the virtual functions
in <B>Instrument</B> print error messages, but that delays the appearance of the
error information until runtime and it requires reliable exhaustive testing on
the part of the user. It is much better to catch the problem at compile
time.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here is the syntax used for a pure
virtual declaration:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>virtual</font> <font color=#0000ff>void</font> f() = 0;</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">By doing this, you tell the compiler to
reserve a slot for a function in the VTABLE<A NAME="Index2485"></A>, but not to
put an address in that particular slot. Even if only one function in a class is
declared as pure virtual, the VTABLE is incomplete.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If the VTABLE for a class is incomplete,
what is the compiler supposed to do when someone tries to make an object of that
class? It cannot safely create an object of an abstract class, so you get an
error message from the compiler. Thus, the compiler guarantees the purity of the
abstract class. By making a class abstract, you ensure that the client
programmer cannot misuse it.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here’s <B>Instrument4.cpp</B>
modified to use pure virtual functions. Because the class has nothing but pure
virtual functions, we call it a <I>pure abstract class</I>:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C15:Instrument5.cpp</font>
<font color=#009900>// Pure abstract base classes</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>:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -