📄 ch21.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
<META NAME="Author" Content="Steph Mineart">
<TITLE>Ch 21 -- Polymorphism</TITLE>
</HEAD>
<BODY
BACKGROUND="bg1.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/bg1.gif" BGCOLOR="#FFFFFF">
<P ALIGN="CENTER"><IMG SRC="sams.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/sams.gif" WIDTH="75" HEIGHT="24" ALIGN="BOTTOM"
BORDER="0"><BR>
<BR>
<A HREF="index-3.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/index.htm"><IMG SRC="toc.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/toc.gif" WIDTH="40" HEIGHT="40" ALIGN="BOTTOM"
ALT="TOC" BORDER="0" NAME="toc4"></A><A HREF="ch20.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/ch20.htm"><IMG SRC="back-1.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/back.gif"
WIDTH="40" HEIGHT="40" ALIGN="BOTTOM" ALT="BACK" BORDER="0" NAME="toc1"></A><A HREF="ch22.htm" tppabs="http://pbs.mcp.com/ebooks/0672310228/ch22.htm"><IMG
SRC="forward.gif" tppabs="http://pbs.mcp.com/ebooks/0672310228/buttonart/forward.gif" WIDTH="40" HEIGHT="40" ALIGN="BOTTOM"
ALT="FORWARD" BORDER="0"
NAME="toc2"></A></P>
<H2 ALIGN="CENTER"><FONT COLOR="#000077">Charlie Calvert's C++ Builder Unleashed</FONT></H2>
<P>
<H2 ALIGN="CENTER"><A NAME="Heading1"></A><FONT COLOR="#000077">- 21 -</FONT></H2>
<H2 ALIGN="CENTER"><A
NAME="Heading2"></A><FONT COLOR="#000077">Polymorphism</FONT></H2>
<P>
<H3><A NAME="Heading3"></A><FONT COLOR="#000077">Overview</FONT></H3>
<P>In this chapter, you will learn about an esoteric but important subject called
polymorphism. If you use
object-oriented programming (OOP) but skip polymorphism,
you miss out on a key tool that yields robust, flexible architectures.</P>
<P>You don't need to understand polymorphism or much about objects to program in
BCB. However, if you want to be an
expert BCB programmer and want to create components,
you should master this material.</P>
<P>In this chapter, I take you through several fairly simple programs designed to
illustrate key aspects of polymorphism. By the time you're done, you should
understand
why the simple ability to assign a child object to a parent object is one of the
most important features of the entire C++ language.
<H3><A NAME="Heading4"></A><FONT COLOR="#000077">Polymorphism from 20,000 Feet</FONT></H3>
<P>Polymorphism
can be confusing even to experienced OOP programmers. My explanation
of this subject starts with a high-level overview of the theoretical issues that
lie at the core of this field of study. The text then moves on to show some real-world
examples and
finally comes back to a second take on the high-level overview. Don't
panic if you don't understand the information in the next few paragraphs right away.
I cover this material several times in several different ways, and by the time you
finish this
chapter, all should be clear to you.</P>
<P>Polymorphism is a technique that allows you to set a parent object equal to one
or more of its child objects:</P>
<PRE><FONT COLOR="#0066FF">Parent = Child;
</FONT></PRE>
<P>The interesting thing about this
technique is that, after the assignment, the
parent acts in different ways, depending on the traits of the child that is currently
assigned to it. One object, the parent, therefore acts in many different ways--hence
the name "polymorphism,"
which translates literally from its Greek roots
to an English phrase similar to "many shapes."
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>I find the words "assign"
and
"equal" extremely tricky. If I'm speaking off the cuff and use the
word "assign" in three consecutive sentences, I will trip over myself for
sure! Because this seemingly simple subject is so tricky, I'm going to take a moment
to
lay out some rules that you can reference while you're reading this chapter. <BR>
<BR>
Consider the following code fragment:</P>
<PRE><FONT COLOR="#0066FF">Parent = Child;</FONT></PRE>
<P>In this simple statement, the child is assigned to its
parent. The parent is not
assigned to its child. You could easily argue that this definition could be reversed,
but I do not take that approach in this chapter. <BR>
<BR>
When referencing the preceding code fragment, I also say that the parent is
set equal
to its child. The child is not set equal to its parent. Once again, you can argue
that this definition could be reversed, or that the action is reflexive. Throughout
this chapter, however, I consistently state that the preceding code sets
a parent
equal to its child, and not a child equal to its parent. <BR>
<BR>
Because these seemingly simple English phrases are so confusing, at least to me,
I try to illustrate exactly what I mean as often as possible. That is, I say the
statement in English, insert a colon, and then follow the English phrase with code
that provides an example of the meaning of my statement. I find the English phrases
ambiguous and confusing, but the code is starkly clear. Don't waste too much time
parsing the English; concentrate on the code!
<HR>
</BLOCKQUOTE>
<P>The classic example of polymorphism is a series of objects, all of which do the
following:
<UL>
<LI>Descend from one base class
<P>
<LI>Respond to a virtual command called
<TT>Draw</TT>
<P>
<LI>Produce different outcomes
</UL>
<P>For instance, you might have four objects called <TT>TRectangle</TT>, <TT>TEllipse</TT>,
<TT>TCircle</TT>, and <TT>TSquare</TT>. Suppose that each of these objects is a descendant
of a base
class called <TT>TShape</TT>, and that <TT>TShape</TT> has a virtual method
called <TT>Draw</TT>. (This hypothetical <TT>TShape</TT> object is not necessarily
the one that appears on BCB's component palette.) All the children of <TT>TShape</TT>
also
have <TT>Draw</TT> methods, but one draws a circle; one, a square; the next,
a rectangle; and the last, an ellipse. You could then assign any of these objects
to a variable of type <TT>TShape</TT>, and that <TT>TShape</TT> variable would act
differently after each assignment. That is, the object of type <TT>TShape</TT> would
draw a square if set equal to a <TT>TSquare</TT> object:</P>
<PRE><FONT COLOR="#0066FF">TShape *Shape = Square;
Shape->Draw(); // Draws a square.
</FONT></PRE>
<P>It would draw an ellipse if set equal to a <TT>TEllipse</TT> object:</P>
<PRE><FONT COLOR="#0066FF">TShape *Shape = Ellipse;
Shape->Draw(); // Draws an ellipse;
</FONT></PRE>
<P>and so on.</P>
<P>Notice that in both these cases the object
that does the drawing is of type <TT>TShape</TT>.
In both cases, the same command of <TT>TShape</TT> is called. You would therefore
expect that a call to <TT>Shape->Draw()</TT> would always produce the same results.
Polymorphism puts the lie to
this logic. It allows a method of an object to act in
many different ways. One object, called <TT>Shape</TT>, "morphs" from one
set of functionality to another, depending on the context of the call. That's polymorphism.</P>
<P>From a
conceptual point of view, this description does much to explain what polymorphism
is all about. However, I still need to explain one key aspect.</P>
<P>According to the rules of OOP, you can pass all these objects to a single function
that takes an
object of type <TT>TShape</TT> as a parameter. That single function
can call the <TT>Draw</TT> method of each of these objects, and each one will behave
differently:</P>
<PRE><FONT COLOR="#0066FF">void DrawIt(TShape *Shape)
{
Shape->Draw(); //
TShape draws different shapes depending on "assignment"
}
void DoSomething()
{
TRectangle *Rectangle = new TRectangle();
TSquare *Square = new TSquare();
TEllipse *Ellipse = new TEllipse();
DrawIt(Rectangle); //
Draws a rectangle
DrawIt(Square); // Draws a square
DrawIt(Ellipse); // Draws an ellipse
delete Rectangle;
delete Square;
delete Ellipse;
}
</FONT></PRE>
<P>When you pass an object of type
<TT>TRectangle</TT> to a function that takes a
<TT>TShape</TT> as a parameter, you are accessing the <TT>TRectangle</TT> object
through an object of type <TT>TShape</TT>. Or, if you look at the act of passing
a parameter from a slightly different
angle, you're actually assigning a variable
of type <TT>TRectangle</TT> to a variable of type <TT>TShape</TT>:</P>
<PRE><FONT COLOR="#0066FF">Shape = Rectangle;
</FONT></PRE>
<P>This assignment is the actual hub around which polymorphism revolves.
Because
this assignment is legal, you can use an object of a single type yet have it behave
in many different ways: Once again, that's polymorphism.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B>
</B>Grasping the idea behind polymorphism
is a bit like grasping the idea behind pointers. Many programmers have a hard time
understanding pointers when they first see them. Then, after a time, manipulating
pointers becomes as natural as tying
their shoes. It no longer requires thought.
The same is true of polymorphism. The concept can seem quite opaque at first to many
programmers, and then they have a little epiphany. Then wham! Suddenly their coding
ability makes the same kind of huge
jump forward that occurred when they learned
about pointers. <BR>
<BR>
Polymorphism has the same relationship to OOP that pointers have to C or C++. People
might say they are C++ programmers, but if they don't understand pointers, they are
missing out on at least half of what the language has to offer. The same is true
of OOP and polymorphism. Many programmers claim to understand OOP, but if they don't
yet see what polymorphism is all about, they are missing at least half the power
of technology.
<HR>
</BLOCKQUOTE>
<P>To fully understand the preceding few paragraphs, you have to grasp that children
of an object are assignment-compatible with their parents. Consider the following
declaration:</P>
<PRE><FONT
COLOR="#0066FF">class TParent
{
}
class TChild: public TParent
{
}
Given these declarations, the following is legal:
{
TParent *Parent;
TChild *Child;
Parent = Child;
}
</FONT></PRE>
<P>But this syntax is flagged as a type
mismatch; that is, the compiler will complain
that it cannot convert a variable of type <TT>TParent*</TT> to <TT>TChild*</TT>:</P>
<PRE><FONT COLOR="#0066FF">{
TParent *Parent;
TChild *Child;
Child = Parent;
}
</FONT></PRE>
<P>You can't set
a child equal to a parent because the child is larger than its parent--that
is, it has more methods or fields--and therefore all its fields and methods will
not be filled out by the assignment. All other things being equal, you can build
a two-story
building out of the pieces meant for a three-story building; but you
can't build a three-story building out of the pieces meant for a two-story building.</P>
<P>Consider the following hierarchy:</P>
<PRE><FONT COLOR="#0066FF">class TParent: public
TObject
{
virtual void Draw();
};
class TChild: public TParent
{
virtual void Draw();
virtual void ShowHierarchy();
};
</FONT></PRE>
<P>The issue here is that setting a child equal to a parent is not safe:</P>
<PRE><FONT
COLOR="#0066FF">Child = Parent; // Don't do this!
</FONT></PRE>
<P>If it were allowed, writing the following would be a disaster:</P>
<PRE><FONT COLOR="#0066FF">Child->ShowHierarchy();
</FONT></PRE>
<P>In this hypothetical world, the call might
compile, but it would fail at runtime
because <TT>Parent</TT> has no <TT>ShowHierarchy</TT> method; therefore, it could
not provide a valid address for the function at the time of the assignment operation.
I will return to this subject in the next
section of this chapter.</P>
<P>If you set a parent equal to a child, all the features of the parent will be filled
out properly:</P>
<PRE><FONT COLOR="#0066FF">Parent = Child;
</FONT></PRE>
<P>That is, all the functions of <TT>TParent</TT> are part
of <TT>TChild</TT>, so
you can assign one to the other without fear of something going wrong. The methods
that are not part of <TT>TParent</TT> are ignored.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B>
</B>When you're thinking about this
material, you need to be sure you are reading statements about assigning parents
to children correctly. Even if I manage to straighten out my grammar, nothing in
the English language makes totally clear which
item in an assignment statement is
on the left and which is on the right. I could use the terms <TT>lvalue</TT> and
<TT>rvalue</TT> in this case, except that they don't quite fit. However, if you take
this description as an analogy, you can
consider a child to be an <TT>rvalue</TT>
and a parent to be an <TT>lvalue</TT>. You can set a parent equal to a child:</P>
<PRE><FONT COLOR="#0066FF">Parent = Child;</FONT></PRE>
<P>but you can't set a child equal to a parent:</P>
<PRE><FONT
COLOR="#0066FF">Child = Parent;</FONT></PRE>
<P>You literally can't do this. You get a type mismatch. The compiler replies "Cannot
convert <TT>TParent*</TT> to <TT>TChild</TT>." In this sense, <TT>Child</TT>
becomes like an
<TT>rvalue</TT> in this one case. Even though assigning values to
<TT>Child</TT> is ultimately possible, you can't assign a <TT>Parent</TT> to it.
In this one case, it might as well be an <TT>rvalue</TT>. <BR>
<BR>
To see this process in action,
start a new project in the IDE, drop a button on the
form, and write the following code:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Button1Click(TObject *Sender)
{
TForm *Form;
TComponent *Component;
Component = Form;
Form =
Component;
}</FONT></PRE>
<P>The compiler will allow the first assignment but object to the second. This objection
occurs because <TT>TForm</TT> is a descendant of <TT>TComponent</TT>.
<HR>
</BLOCKQUOTE>
<H3><A NAME="Heading8"></A><FONT
COLOR="#000077">Another View of Polymorphism</FONT></H3>
<P>Here's another way of looking at polymorphism. A base class defines a certain
number of functions that are inherited by all its descendants. If you assign a variable
of the child type to one
of its parents, all the parent's methods are guaranteed
to be filled out with valid addresses. The issue here is that the child, by the very
fact of its being a descendant object, must have valid addresses for all the methods
used in its parent's
virtual method table (VMT). As a result, you can call one of
these methods and watch as the child's functions get called. However, you cannot
call one of the child's methods that does not also belong to the parent. The parent
doesn't know about those
methods, so the compiler won't let you call them. In other
words, the parent may be able to call some of the child's functions, but it is still
a variable of the parent type.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>A virtual method table, or VMT,
is a table maintained in memory by the compiler; it contains a list of all the pointers
to the virtual methods hosted by an object. If you have an object that is descended
from <TT>TObject</TT>, the VMT for that object will contain all the virtual methods
of that object, plus the virtual methods of <TT>TObject</TT>.
<HR>
</BLOCKQUOTE>
<P>To help you understand this arrangement, picture the VMT for <TT>TParent</TT>.
It has a pointer to a <TT>Draw</TT> method in it, but no pointer to a <TT>ShowHierarchy</TT>
method. Therefore, an attempt to call its <TT>ShowHierarchy</TT> method would fail,
as would an attempt to fill out a <TT>TChild</TT>'s <TT>ShowHierarchy</TT>
through
an assignment with a <TT>TParent</TT> object.</P>
<P>Consider this schema:</P>
<PRE><FONT COLOR="#0066FF">Simplified VMT for Parent Simplified VMT for Child
StartTable StartTable
Draw
------------------------------ Draw
EndTable ShowHierarchy
EndTable
</FONT></PRE>
<P>This schema shows a parent being set equal to a child. As you can see, the address
of the
<TT>Draw</TT> method for the parent is assigned to the address for the <TT>Draw</TT>
method for the child. No <TT>ShowHierarchy</TT> method exists in the parent, so it
is ignored.</P>
<P>Here's what happens if you try to set the child equal to the
parent:</P>
<PRE><FONT COLOR="#0066FF">Simplified VMT for Child Simplified VMT for Parent
StartTable StartTable
Draw ------------------------------ Draw
ShowHierarchy -------------------- ????
EndTable
EndTable
</FONT></PRE>
<P>As you can clearly see, no method pointer in the parent table can be assigned
to the <TT>ShowHierarchy</TT> method of the child table. Therefore, it is left blank,
which means a call to the <TT>ShowHierarchy</TT> method of
the child almost certainly
fails.</P>
<P>Because the <TT>ShowHierarchy</TT> method cannot be filled out properly, assigning
<TT>TParent</TT> to <TT>TChild</TT> is illegal. In other words, it's not just a legal
technicality, it's a literal
impossibility. You literally can't successfully assign
a parent to a child. You can, however, assign a child to a parent, and it's this
assignment that lies at the heart of polymorphism. For the sake of clarity, let me
spell it out. Here is the legal
assignment:</P>
<PRE><FONT COLOR="#0066FF">Parent = Child;
</FONT></PRE>
<P>Here is the illegal assignment:</P>
<PRE><FONT COLOR="#0066FF">Child = Parent;
</FONT></PRE>
<P>Needless to say, I wouldn't be placing so much emphasis on this subject if it
were not vitally important. You simply won't really understand OOP unless you grasp
what has been said in the last few pages.
<H3><A NAME="Heading10"></A><FONT COLOR="#000077">Virtual Methods and Polymorphism</FONT></H3>
<P>If some of the methods in a
base class are defined as virtual, each of the descendants
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -