📄 mc1.htm
字号:
</UL><A NAME="81227"></A>
<P><A NAME="dingp44"></A>
These approximations won't be as safe as the real things, of course, but they will simplify the process of upgrading your code when your compilers support the new <NOBR>casts.<SCRIPT>create_link(44);</SCRIPT>
</NOBR></P><A NAME="78697"></A>
<A NAME="p16"></A><P><A NAME="dingp45"></A>
There is no easy way to emulate the behavior of a <CODE>dynamic_cast</CODE>, but many libraries provide functions to perform safe inheritance-based casts for you. If you lack such functions and you <I>must</I> perform this type of cast, you can fall back on C-style casts for those, too, but then you forego the ability to tell if the casts fail. Needless to say, you can define a macro to look like <CODE>dynamic_cast</CODE>, just as you can for the other <NOBR>casts:<SCRIPT>create_link(45);</SCRIPT>
</NOBR></P><A NAME="81220"></A>
<UL><PRE>#define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
</PRE>
</UL><A NAME="81269"></A>
<P><A NAME="dingp46"></A>
Remember that this approximation is not performing a true <CODE>dynamic_cast</CODE>; there is no way to tell if the cast <NOBR>fails.<SCRIPT>create_link(46);</SCRIPT>
</NOBR></P><A NAME="84816"></A>
<P><A NAME="dingp47"></A>
I know, I know, the new casts are ugly and hard to type. If you find them too unpleasant to look at, take solace in the knowledge that C-style casts continue to be valid. However, what the new casts lack in beauty they make up for in precision of meaning and easy recognizability. Programs that use the new casts are easier to parse (both for humans and for tools), and they allow compilers to diagnose casting errors that would otherwise go undetected. These are powerful arguments for abandoning C-style casts, and there may also be a third: perhaps making casts ugly and hard to type is a <I>good</I> <NOBR>thing.<SCRIPT>create_link(47);</SCRIPT>
</NOBR></P>
<!-- SectionName="M3: Never treat arrays polymorphically." -->
<A NAME="84818"></A><DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MC1.HTM#77216">Item 2: Prefer C++-style casts</A> <BR> Continue to <A HREF="./MC1.HTM#5218">Item 4: Avoid gratuitous default constructors</A></FONT></DIV>
<P><A NAME="dingp48"></A><font ID="mititle">Item 3: Never treat arrays polymorphically.</font><SCRIPT>create_link(48);</SCRIPT>
</P>
<A NAME="72107"></A>
<A NAME="31585"></A>
<P><A NAME="dingp49"></A>
One of the most important features of inheritance is that you can manipulate derived class objects through pointers and references to base class objects. Such pointers and references are said to behave <I>polymorphically</I> — as if they had multiple types. C++ also allows you to manipulate <I>arrays</I> of derived class objects through base class pointers and references. This is no feature at all, because it almost never works the way you want it <NOBR>to.<SCRIPT>create_link(49);</SCRIPT>
</NOBR></P><A NAME="77400"></A>
<P><A NAME="dingp50"></A>
For example, suppose you have a class <CODE>BST</CODE> (for binary search tree objects) and a second class, <CODE>BalancedBST</CODE>, that inherits from <CODE>BST</CODE>:<SCRIPT>create_link(50);</SCRIPT>
</P>
<A NAME="77401"></A>
<UL><PRE>class BST { ... };
<A NAME="77402"></A>
class BalancedBST: public BST { ... };</PRE>
</UL>
<A NAME="77403"></A>
<P><A NAME="dingp51"></A>
In a real program such classes would be templates, but that's unimportant here, and adding all the template syntax just makes things harder to read. For this discussion, we'll assume <CODE>BST</CODE> and <CODE>BalancedBST</CODE> objects contain only <CODE>int</CODE>s.<SCRIPT>create_link(51);</SCRIPT>
</P><A NAME="77404"></A>
<P><A NAME="dingp52"></A>
Consider a function to print out the contents of each <CODE>BST</CODE> in an array of <CODE>BST</CODE>s:<SCRIPT>create_link(52);</SCRIPT>
</P><A NAME="77406"></A>
<UL><A NAME="p17"></A>
<PRE>
void printBSTArray(ostream& s,
const BST array[],
int numElements)
{
for (int i = 0; i < numElements; ++i) {
s << array[i]; // this assumes an
} // operator<< is defined
} // for BST objects
</PRE>
</UL>
<A NAME="77407"></A>
<P><A NAME="dingp53"></A>
This will work fine when you pass it an array of <CODE>BST</CODE> <NOBR>objects:<SCRIPT>create_link(53);</SCRIPT>
</NOBR></P><A NAME="77408"></A>
<UL><PRE>BST BSTArray[10];
<A NAME="77409"></A>
...
<A NAME="77410"></A>
printBSTArray(cout, BSTArray, 10); // works fine</PRE>
</UL>
<A NAME="77411"></A>
<P><A NAME="dingp54"></A>
Consider, however, what happens when you pass <CODE>printBSTArray</CODE> an array of <CODE>BalancedBST</CODE> <NOBR>objects:<SCRIPT>create_link(54);</SCRIPT>
</NOBR></P><A NAME="77412"></A>
<UL><PRE>BalancedBST bBSTArray[10];
<A NAME="77413"></A>
...
<A NAME="77414"></A>
printBSTArray(cout, bBSTArray, 10); // works fine?</PRE>
</UL>
<A NAME="77415"></A>
<P><A NAME="dingp55"></A>
Your compilers will accept this function call without complaint, but look again at the loop for which they must generate <NOBR>code:<SCRIPT>create_link(55);</SCRIPT>
</NOBR></P><A NAME="77416"></A>
<UL><PRE>for (int i = 0; i < numElements; ++i) {
s << array[i];
}</PRE>
</UL>
<A NAME="77417"></A>
<P><A NAME="dingp56"></A>
Now, <CODE>array[i]</CODE> is really just shorthand for an expression involving pointer arithmetic: it stands for <CODE>*(array+i)</CODE>. We know that <CODE>array</CODE> is a pointer to the beginning of the array, but how far away from the memory location pointed to by <CODE>array</CODE> is the memory location pointed to by <CODE>array+i</CODE>? The distance between them is <CODE>i*sizeof(<I>an</I></CODE> <CODE><I>object</I></CODE> <CODE><I>in</I></CODE> <CODE><I>the</I></CODE> <CODE><I>array</I></CODE><CODE>)</CODE>, because there are <CODE>i</CODE> objects between <CODE>array[0]</CODE> and <CODE>array[i]</CODE>. In order for compilers to emit code that walks through the array correctly, they must be able to determine the size of the objects in the array. This is easy for them to do. The parameter <CODE>array</CODE> is declared to be of type array-of-<CODE>BST</CODE>, so each element of the array must be a <CODE>BST</CODE>, and the distance between <CODE>array</CODE> and <CODE>array+i</CODE> must be <CODE>i*sizeof(BST)</CODE>.<SCRIPT>create_link(56);</SCRIPT>
</P><A NAME="77418"></A>
<P><A NAME="dingp57"></A>
At least that's how your compilers look at it. But if you've passed an array of <CODE>BalancedBST</CODE> objects to <CODE>printBSTArray</CODE>, your compilers are probably wrong. In that case, they'd assume each object in the array is the size of a <CODE>BST</CODE>, but each object would actually be the size of a <CODE>BalancedBST</CODE>. Derived classes usually have more data members than their base classes, so derived class objects are usually larger than base class objects. We thus expect a <CODE>BalancedBST</CODE> object to be larger than a <A NAME="p18"></A><CODE>BST</CODE> object. If it is, the pointer arithmetic generated for <CODE>printBSTArray</CODE> will be wrong for arrays of <CODE>BalancedBST</CODE> objects, and there's no telling what will happen when <CODE>printBSTArray</CODE> is invoked on a <CODE>BalancedBST</CODE> array. Whatever does happen, it's a good bet it won't be <NOBR>pleasant.<SCRIPT>create_link(57);</SCRIPT>
</NOBR></P><A NAME="77419"></A>
<P><A NAME="dingp58"></A>
The problem pops up in a different guise if you try to delete an array of derived class objects through a base class pointer. Here's one way you might innocently attempt to do <NOBR>it:<SCRIPT>create_link(58);</SCRIPT>
</NOBR></P>
<A NAME="77420"></A>
<UL><PRE>// delete an array, but first log a message about its
// deletion
void deleteArray(ostream& logStream, BST array[])
{
logStream << "Deleting array at address "
<< static_cast<void*>(array) << '\n';
<A NAME="77421"></A>
delete [] array;
}
<A NAME="77422"></A>
<CODE>BalancedBST</CODE> *balTreeArray = // create a <CODE>BalancedBST</CODE>
new <CODE>BalancedBST</CODE>[50]; // array
<A NAME="77423"></A>
...
<A NAME="77424"></A>
deleteArray(cout, balTreeArray); // log its deletion</PRE>
</UL>
<A NAME="77425"></A>
<P><A NAME="dingp59"></A>
You can't see it, but there's pointer arithmetic going on here, too. When an array is deleted, a destructor for each element of the array must be called (see <A HREF="./MC2_FR.HTM#33985" TARGET="_top" onMouseOver = "self.status = 'Link to MEC++ Item 8'; return true" onMouseOut = "self.status = self.defaultStatus">Item 8</A>). When compilers see the <NOBR>statement<SCRIPT>create_link(59);</SCRIPT>
</NOBR></p>
<A NAME="77429"></A>
<UL><PRE>delete [] array;</PRE>
</UL>
<A NAME="77430"></A><p><A NAME="dingp60"></A>
they must generate code that does something like <NOBR>this:<SCRIPT>create_link(60);</SCRIPT>
</NOBR></P>
<A NAME="77431"></A>
<UL><PRE>// destruct the objects in *array in the inverse order
// in which they were constructed
for (int i = <I>the number of elements in the array - 1</I>;
i >= 0;
--i)
{
array[i].BST::~BST(); // call array[i]'s
} // destructor</PRE>
</UL>
<A NAME="77432"></A>
<P><A NAME="dingp61"></A>
Just as this kind of loop failed to work when you wrote it, it will fail to work when your compilers write it, too. The <NOBR><FONT COLOR="#FF0000" SIZE="-2"><B>°</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=cstandard" onMouseOver="self.status='ISO/ANSI standard for C++'; return true" onMouseOut="self.status=self.defaultStatus" target="_top">language</NOBR> specification</A> says the result of deleting an array of derived class objects through a base class pointer is undefined, but we know what that really means: executing the code is almost certain to lead to grief. Polymorphism and pointer arithmetic simply don't mix. Array operations almost always involve pointer arithmetic, so arrays and polymorphism don't <NOBR>mix.<SCRIPT>create_link(61);</SCRIPT>
</NOBR></P><A NAME="92807"></A>
<P><A NAME="dingp62"></A>
Note that you're unlikely to make the mistake of treating an array polymorphically if you avoid having a concrete class (like <CODE>BalancedBST</CODE>) in<A NAME="p19"></A>herit from another concrete class (such as <CODE>BST</CODE>). As <A HREF="./MC6_FR.HTM#10947" TARGET="_top" onMouseOver = "self.status = 'Link to MEC++ Item 33'; return true" onMouseOut = "self.status = self.defaultStatus">Item 33</A> explains, designing your software so that concrete classes never inherit from one another has many benefits. I encourage you to turn to <A HREF="./MC6_FR.HTM#10947" TARGET="_top" onMouseOver = "self.status = 'Link to MEC++ Item 33'; return true" onMouseOut = "self.status = self.defaultStatus">Item 33</A> and read all about <NOBR>them.<SCRIPT>create_link(62);</SCRIPT>
</NOBR></P>
<!-- SectionName="M4: Avoid gratuitous default constructors." -->
<A NAME="5218"></A><DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MC1.HTM#84818">Item 3: Never treat arrays polymorphically</A> <BR> Continue to <A HREF="./MC2.HTM#5701" TARGET="_top">Operators</A></FONT></DIV>
<P><A NAME="dingp63"></A><font ID="mititle">Item 4: Avoid gratuitous default constructors.</font><SCRIPT>create_link(63);</SCRIPT>
</P>
<A NAME="72108"></A>
<A NAME="39909"></A>
<P><A NAME="dingp64"></A>
A default constructor (i.e., a constructor that can be called with no arguments) is the C++ way of saying you can get something for nothing. Constructors initialize objects, so default constructors initialize objects without any information from the place where the object is being created. Sometimes this makes perfect sense. Objects that act like numbers, for example, may reasonably be initialized to zero or to undefined values. Objects that act like pointers (
<A HREF="./MC5_FR.HTM#61766" TARGET="_top" onMouseOver = "self.status = 'Link to MEC++ Item 28'; return true" onMouseOut = "self.status = self.defaultStatus">Item 28</A>) may reasonably be initialized to null or to undefined values. Data structures like linked lists, hash tables, maps, and the like may reasonably be initialized to empty <NOBR>containers.<SCRIPT>create_link(64);</SCRIPT>
</NOBR></p><A NAME="39967"></A>
<P><A NAME="dingp65"></A>
Not all objects fall into this category. For many objects, there is no reasonable way to perform a complete initialization in the absence of outside information. For example, an object representing an entry in an address book makes no sense unless the name of the thing being entered is provided. In some companies, all equipment must be tagged with a corporate ID number, and creating an object to model a piece of equipment in such companies is nonsensical unless the appropriate ID number is <NOBR>provided.<SCRIPT>create_link(65);</SCRIPT>
</NOBR></p><A NAME="39989"></A>
<P><A NAME="dingp66"></A>
In a perfect world, classes in which objects could reasonably be created from nothing would contain default constructors and classes in which information was required for object construction would not. Alas, ours is not the best of all possible worlds, so we must take additional concerns into account. In particular, if a class lacks a default constructor, there are restrictions on how you can use that <NOBR>class.<SCRIPT>create_link(66);</SCRIPT>
</NOBR></p><A NAME="40014"></A>
<P><A NAME="dingp67"></A>
Consider a class for company equipment in which the corporate ID number of the equipment is a mandatory constructor <NOBR>argument:<SCRIPT>create_link(67);</SCRIPT>
</NOBR></p><A NAME="40028"></A>
<UL><PRE>class EquipmentPiece {
public:
EquipmentPiece(int IDNumber);
...
};</PRE>
</UL>
<A NAME="40047"></A>
<P><A NAME="dingp68"></A>
Because <CODE>EquipmentPiece</CODE> lacks a default constructor, its use may be problematic in three contexts. The first is the creation of arrays. There <A NAME="p20"></A>is, in general, no way to specify constructor arguments for objects in arrays, so it is not usually possible to create arrays of <CODE>EquipmentPiece</CODE> <NOBR>objects:<SCRIPT>create_link(68);</SCRIPT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -