📄 chapter 10 virtual methods.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0063)http://topaz.cs.byu.edu/text/html/Textbook/Chapter10/index.html -->
<HTML><HEAD><TITLE>Chapter 10: Virtual Methods</TITLE>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
<META content="MSHTML 6.00.2800.1458" name=GENERATOR></HEAD>
<BODY>
<CENTER>
<H1>Chapter 10<BR>Virtual Methods</H1></CENTER>
<HR>
<!-------------------------------------------------------------------------------->
<H2>10.1 What are Virtual Methods?</H2><!-------------------------------------------------------------------------------->Virtual
methods are one of the central features of object oriented programming. They
allow programmers to manipulate groups of objects through a standard interface.
Consider a class, <PRE> type
Shape is class
virtual proc WhoAmI;
begin
write "I am some general shape...\n";
end proc;
end class;
<B>Listing {CLSSHP}.</B> A class with one virtual method.
</PRE>Notice the presence of the keyword <TT>virtual</TT> in front of the
declaration of the procedure method, <TT>WhoAmI()</TT>. This keyword tells the
compiler that other subclasses will have their own inplementations of function
<TT>WhoAmI()</TT> that should be used, instead.
<P>We can make an instance of this class, but it wouldn't do much. This class
was meant to be a virtual base class. There is nothing really special about such
a class except in the way that it is <I>used</I>. The idea is that we declare
such a class and use it as a standard way of manipulating other classes that
will inherit from it.
<P>Suppose we have three other classes as shown in listing {CLSSHPS}. <PRE> type
Circle is class
extends Shape;
proc WhoAmI;
begin
write "I am a Circle.\n";
end proc;
end class;
Square is class
extends Shape;
proc WhoAmI;
begin
write "I am a Square.\n";
end proc;
end class;
Triangle is class
extends Shape;
proc WhoAmI;
begin
write "I am a Triangle.\n";
end proc;
end class;
<B>Listing {CLSSHP}.</B> Three classes, <TT>Circle</TT>, <TT>Square</TT>, and <TT>Triangle</TT>
inherit from a general base class, <TT>Shape</TT>. Using a pointer to <TT>Shape</TT>, we
can manipulate any of these other classes.
</PRE>There are now three classes that are more useful, and all of them can be
controlled through their <TT>Shape</TT> base class. We can do many things with
such a concept. We can make an abstract data type like a stack or a list or a
tree that stores <TT>Shape</TT> instances, and we can store anything that
inherits from a <TT>Shape</TT> superclass.
<P>The way to make use of a group of classes that have a common virtual base
class is to make a pointer to the common base class. suppose we had a function
that iterated through a list of shapes and returned a pointer to a
<TT>Shape</TT>. <PRE> func GetNext: ^Shape;
</PRE>Every time we call <TT>GetNext()</TT> we will get the next shape in the
list, until there are no more. With each shape, we can tell it to tell us who it
is. <PRE> var
pshape: ^shape;
begin
pshape:= GetNext();
while pshape <> null do
pshape^.WhoAmI();
pshape:= GetNext();
loop;
end
</PRE>One of the principles central to object oriented programming is that we do
away with building separate cases for all different types of things. Instead we
make each thing become a class that is controlled through a standard interface,
and tell each class, "Go and do that thing that you're supposed to do instead,
when this method of your base class is called." This is commonly called message
passing.
<P>Under SAL, virtual methods are only used under the following circumstances:
<OL>
<LI>A class has any method, <TT>x()</TT> that is declared with the
<TT>virtual</TT> keyword.
<LI>A deriving class (either direct or indirect) has a maching method whose
declaration is identical to <TT>x()</TT>. Parameter list and return type must
be the same.
<LI>The method <TT>x()</TT> is called through a pointer or a reference to any
instance inheriting (either directly or indirectly) from the base class
containing the original definition for <TT>x()</TT>. </LI></OL>
<H3>10.1.1 Virtual Methods and Shadowing</H3><!-------------------------------------------------------------------------------->There
is a subtle difference between virtual methods and shadowing. One is from the
point of view of the base class, and the other is from the point of view of the
derived class. Shadowing occurrs when we have an instance of a derived class
that is a variable, i.e., not a pointer or a reference. To demonstrate
shadowing, suppose we have two variables, a, and b, like so: of <TT>Shape</TT>
like so, <PRE> var
a: Shape;
b: Square;
</PRE>When we call <TT>a.WhoAmI()</TT>, we get a message that says <PRE> I am some general shape...
</PRE>and when call <TT>b.WhoAmI()</TT>, we would get a message that says <PRE> I am a Square.
</PRE>In the case of the instance of <TT>Square</TT> the call to
<TT>Shape::WhoAmI()</TT> was shadowed by <TT>Square::WhoAmI()</TT>.
<P>The virtual method concept makes sure that this behavior still holds when all
we have is a pointer to a <TT>Shape</TT>. The actual instance may be a piece of
a <TT>Circle</TT> or a <TT>Triangle</TT> or any other class that inherits from
<TT>Shape</TT>, but it will be all the same to us, because all we really need to
manipulate each object is the standard interface that we have defined.
<P><!-------------------------------------------------------------------------------->
<H2>10.2 How Virtual Methods Work</H2><!-------------------------------------------------------------------------------->Virtual
methods work by making use of virtual tables, or vtables (V-tables) for short,
and another mechanism called virtual indexing. Since a compiler is oblidged to
compile a base class that will be made use of at a later date, virtual methods
are only something that can be resolved at runtime. A vtable is a runtime
component that tells a program what function it should call instead of the one
that the author originally stated. All classes in SAL contain a pointer to a
vtable. If the class has no virtual methods, then the pointer is set to
<TT>null</TT>. The vtable pointer is always the first four bytes of every class
instance.
<P>
<CENTER><IMG height=230 src="Chapter 10 Virtual Methods.files/GNRVFT.gif"
width=640></CENTER>In SAL each vtable has a four-byte marker to identify its
contents in memory. The bytes are ascii text, and are the letters "VTBL".
Following this marker is the vtable data. Each class's vtable pointer points
directly to this data. The data consists of a list of tuples, where the first
element of each tuple is a pointer to the actual function to be called, and the
second element is a delta to be added to the pointer to self.
<P>
<H3>10.2.1 Virtual Methods in Single Inheritance</H3><!-------------------------------------------------------------------------------->In
addition to the vtable, each virtual function belonging to a class has its own
index into the virtual table. Let's consider a new class with a couple of
virtual methods and some member variables: <PRE> type
C is class
i, j, k: int;
virtual proc foo;
begin
write "I am C::foo().\n";
enc proc;
virtual proc bar;
begin
write "I am C::bar().\n";
enc proc;
end class;
<B>Listing {CLSVC}.</B> A base class with two virtual functions.
</PRE>Since class <TT>C</TT> has no superclasses, the indicies for its virtual
functions begins at zero (or they can begin at one depending on the base of the
count). Method <TT>foo()</TT> is declared, and so its virtual index is zero.
Method <TT>bar()</TT> comes next, and its index is one. If class <TT>C</TT> had
more virtual methods, then the indexing would proceed to two, and then three,
and so on until all methods that were virtual had been indexed. A memory layout
for class <TT>C</TT> would look like figure {MEMVC}
<CENTER><IMG src="Chapter 10 Virtual Methods.files/MEMVC.gif"></CENTER>With
single inheritance, each subclass shares a vtable pointer with the super-most
class. In listing {CLSVBA}, classes <TT>B</TT> and <TT>A</TT> subclass class
<TT>C</TT>. Class <TT>B</TT> has two virtual methods. One is virtual by the fact
that it matches a method in class <TT>C</TT>, and the other is declared virtual.
Class <TT>A</TT> also has two virtual methods, and overrides one of the methods
in <TT>B</TT> and one of the methods of <TT>C</TT>. <PRE> type
B is class
extends C;
a, b, c: int;
proc bar;
begin
write "I am B::bar().\n";
enc proc;
virtual proc baz;
begin
write "I am B::baz().\n";
enc proc;
end class;
A is class
extends B;
p, q, r: int;
proc foo;
begin
write "I am A::foo().\n";
enc proc;
proc baz;
begin
write "I am A::baz().\n";
enc proc;
end class;
<B>Listing {CLSVBA}.</B> Class B extends C, and overrides method C::bar().
B also has a virtual method called baz(). Class A overrides C::foo() and
B::baz().
</PRE>Since class <TT>B</TT> inherits from <TT>C</TT>, it continues indexing
where <TT>C</TT> left off. Thus, <TT>B</TT>'s <TT>bar()</TT> is numbered 2, and
<TT>baz()</TT> gets numbered 3. In a similar manner, when class <TT>A</TT>
inherits <TT>B</TT>, it continues numbering where <TT>B</TT> leaves off. Its
<TT>foo()</TT> is numbered 4, and its <TT>baz()</TT> is numbered 5. If class
<TT>B</TT> had no virtual functions, then <TT>A</TT> would have began indexing
where <TT>C</TT> left off, and <TT>B</TT> would have no entries in its own
vtable and no entries in <TT>A</TT>'s vtable.
<P>With the vtables for classes <TT>B</TT> and <TT>A</TT>, something slightly
different happens. For every method in class <TT>B</TT> that matches a virtual
method in class <TT>C</TT>, the entries in class <TT>C</TT>'s portion of
<TT>B</TT>'s vtable are altered so that <TT>B</TT>'s method gets called,
instead. A similar thing happens for class <TT>A</TT>. It replaces methods for
both classes <TT>C</TT> and <TT>B</TT>. Memory layouts for classes <TT>B</TT>
and <TT>A</TT> whith their vtables are shown in figure {MEMVBA}.
<P>
<CENTER><IMG src="Chapter 10 Virtual Methods.files/MEMVBA.gif"></CENTER>In the
diagram above, we use a certain notation for indexing the vtables. Although this
notation is not used internally by the compiler, it is useful to see how the
vtable is divided up. The notation is read as <PRE> Classname1Index#.Classname2Index#.Classname3Index#....
</PRE>Where Classname1 is the containing class, and Classname2 and the rest are
base classes. In figure {MEMVBA} the first item of the first vtable is indexed
as B0.C0. This means that the first entry is class B's zeroth entry and class
C's zeroth entry. This notation will become especially meaningful when we start
to talk about multiple inheritance.
<P>Notice the change in the vtable for class <TT>B</TT> (Top part of figure
{MEMVBA}, entry B1.C1. <TT>C::bar()</TT> has been substituted with
<TT>B::bar()</TT>. A similar thing has happened in the vtable for class
<TT>A</TT>; entries A0.B0.C0 and A3.B3 are different. <TT>C::foo()</TT> was
replaced by <TT>A::foo()</TT>, and <TT>B::baz()</TT> was replaced by
<TT>A::baz()</TT>.
<P>The vtable layouts for classes <TT>C</TT>, <TT>B</TT>, and <TT>A</TT> all
contain zeros for the delta column. This is because the instances of these three
classes all start at the same spots in memory. A pointer to an instance of class
<TT>A</TT> also points to an instance of <TT>B</TT> and an instance of
<TT>C</TT>. This is true for all pointers to instances of <TT>A</TT>.
<P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -