📄 ei14.htm
字号:
<UL><PRE>// class for representing 2D points
class Point {
public:
Point(short int xCoord, short int yCoord);
~Point();
</PRE>
</UL><A NAME="28510"></A>
<UL><PRE>private:
short int x, y;
};
</PRE>
</UL>
<P><A NAME="dingp13"></A><A NAME="2156"></A>
If a <CODE>short</CODE> <CODE>int</CODE> occupies 16 bits, a <CODE>Point</CODE> object can fit into a 32-bit register. Furthermore, a <CODE>Point</CODE> object can be passed as a 32-bit quantity to functions written in other languages such as C or FORTRAN. If <CODE>Point</CODE>'s destructor is made virtual, however, the situation <NOBR>changes.<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp14"></A><A NAME="2157"></A>The implementation of virtual functions requires that objects carry around with them some additional information that can be used at runtime to determine which virtual functions should be invoked on the object. In most compilers, this extra information takes the form of a pointer called a <CODE>vptr</CODE> ("virtual table pointer"). The <CODE>vptr</CODE> points to an array of function pointers called a <CODE>vtbl</CODE> ("virtual table"); each class with virtual functions has an associated <CODE>vtbl</CODE>. When a virtual function is invoked on an object, the actual function called is determined by following the object's <CODE>vptr</CODE> to a <CODE>vtbl</CODE> and then looking up the appropriate function pointer in the <CODE>vtbl</CODE>.<SCRIPT>create_link(14);</SCRIPT>
</P>
<P><A NAME="dingp15"></A><A NAME="2158"></A>The details of how virtual functions are implemented are unimportant (though, if you're curious, you can read about them in <A HREF="../MEC/MI24_FR.HTM#41284" TARGET="_top">Item M24</A>). What <I>is</I> important is that if the <CODE>Point</CODE> class contains a virtual function, objects of that type will implicitly <I>double</I> in size, from two 16-bit <CODE>short</CODE>s to two 16-bit <CODE>short</CODE>s plus a 32-bit <CODE>vptr</CODE>! No longer will <CODE>Point</CODE> objects fit in a 32-bit register. Furthermore, <CODE>Point</CODE> objects in C++ no <A NAME="p62"></A>longer look like the same structure declared in another language such as C, because their foreign language counterparts will lack the <CODE>vptr</CODE>. As a result, it is no longer possible to pass <CODE>Point</CODE>s to and from functions written in other languages unless you explicitly compensate for the <CODE>vptr</CODE>, which is itself an implementation detail and hence <NOBR>unportable.<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp16"></A><A NAME="2159"></A>The bottom line is that gratuitously declaring all destructors virtual is just as wrong as never declaring them virtual. In fact, many people summarize the situation this way: declare a virtual destructor in a class if and only if that class contains at least one virtual <NOBR>function.<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp17"></A><A NAME="222058"></A>This is a good rule, one that works most of the time, but unfortunately, it is possible to get bitten by the nonvirtual destructor problem even in the absence of virtual functions. For example, <A HREF="./EI13_FR.HTM#2117" TARGET="_top">Item 13</A> considers a class template for implementing arrays with client-defined bounds. Suppose you decide (in spite of the advice in <A HREF="../MEC/MI33_FR.HTM#10947" TARGET="_top">Item M33</A>) to write a template for derived classes representing named arrays, i.e., classes where every array has a <NOBR>name:<SCRIPT>create_link(17);</SCRIPT>
</NOBR></P>
<A NAME="2161"></A>
<UL><PRE>
template<class T> // base class template
class Array { // (from <A HREF="./EI13_FR.HTM#2117" TARGET="_top">Item 13</A>)
public:
Array(int lowBound, int highBound);
~Array();
</PRE>
</UL><A NAME="28518"></A>
<UL><PRE>private:
vector<T> data;
size_t size;
int lBound, hBound;
};
</PRE>
</UL><A NAME="2163"></A>
<UL><PRE>template<class T>
class NamedArray: public Array<T> {
public:
NamedArray(int lowBound, int highBound, const string& name);
...
</PRE>
</UL><A NAME="28524"></A>
<UL><PRE>private:
string arrayName;
};
</PRE>
</UL>
<P><A NAME="dingp18"></A><A NAME="2165"></A>If anywhere in an application you somehow convert a pointer-to-<CODE>NamedArray</CODE> into a pointer-to-<CODE>Array</CODE> and you then use <CODE>delete</CODE> on the <CODE>Array</CODE> pointer, you are instantly transported to the realm of undefined <NOBR>behavior:<SCRIPT>create_link(18);</SCRIPT>
</NOBR></P>
<A NAME="2166"></A>
<UL><PRE>NamedArray<int> *pna =
new NamedArray<int>(10, 20, "Impending Doom");
</PRE>
</UL><A NAME="2167"></A>
<UL><PRE>Array<int> *pa;
</PRE>
</UL><A NAME="2168"></A>
<UL><PRE>...
</PRE>
</UL><A NAME="2169"></A>
<UL><PRE><A NAME="p63"></A>
pa = pna; // NamedArray<int>* -> Array<int>*
</PRE>
</UL><A NAME="2017"></A>
<UL><PRE>...
</PRE>
</UL><A NAME="2018"></A>
<UL><PRE>
delete pa; // undefined! (Insert theme to
//<NOBR><FONT COLOR="#FF0000" SIZE="-2"><B>°</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=twilight" onMouseOver = "self.status = 'Twilight Zone`s Link'; return true" onMouseOut = "self.status = self.defaultStatus" TARGET="_top"></NOBR>Twilight Zone here</A>); in practice,
// pa->arrayName will often be leaked,
// because the NamedArray part of
// *pa will never be destroyed
</PRE>
</UL>
<P><A NAME="dingp19"></A><A NAME="2170"></A>This situation can arise more frequently than you might imagine, because it's not uncommon to want to take an existing class that does something, <CODE>Array</CODE> in this case, and derive from it a class that does all the same things, plus more. <CODE>NamedArray</CODE> doesn't redefine any of the behavior of <CODE>Array</CODE> — it inherits all its functions without change — it just adds some additional capabilities. Yet the nonvirtual destructor problem persists. (As do others. See <A HREF="../MEC/MI33_FR.HTM#10947" TARGET="_top">Item M33</A>.)<SCRIPT>create_link(19);</SCRIPT>
</P>
<P><A NAME="dingp20"></A><A NAME="2174"></A>Finally, it's worth mentioning that it can be convenient to declare pure virtual destructors in some classes. Recall that pure virtual functions result in <I>abstract</I> classes — classes that can't be instantiated (i.e., you can't create objects of that type). Sometimes, however, you have a class that you'd like to be abstract, but you don't happen to have any functions that are pure virtual. What to do? Well, because an abstract class is intended to be used as a base class, and because a base class should have a virtual destructor, and because a pure virtual function yields an abstract class, the solution is simple: declare a pure virtual destructor in the class you want to be <NOBR>abstract.<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp21"></A><A NAME="2175"></A>Here's an <NOBR>example:<SCRIPT>create_link(21);</SCRIPT>
</NOBR></P>
<A NAME="2176"></A>
<UL><PRE>
class AWOV { // AWOV = "Abstract w/o
// Virtuals"
public:
virtual ~AWOV() = 0; // declare pure virtual
// destructor
};
</PRE>
</UL>
<P><A NAME="dingp22"></A><A NAME="2177"></A>This class has a pure virtual function, so it's abstract, and it has a virtual destructor, so you can rest assured that you won't have to worry about the destructor problem. There is one twist, however: you must provide a <I>definition</I> for the pure virtual <NOBR>destructor:<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="2178"></A>
<UL><PRE>
AWOV::~AWOV() {} // definition of pure
// virtual destructor
</PRE>
</UL>
<P><A NAME="dingp23"></A><A NAME="33343"></A>You need this definition, because the way virtual destructors work is that the most derived class's destructor is called first, then the destructor of each base class is called. That means that compilers will generate a call to <CODE>~AWOV</CODE> even though the class is abstract, so you have <A NAME="p64"></A>to be sure to provide a body for the function. If you don't, the linker will complain about a missing symbol, and you'll have to go back and add <NOBR>one.<SCRIPT>create_link(23);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp24"></A><A NAME="33344"></A>You can do anything you like in that function, but, as in the example above, it's not uncommon to have nothing to do. If that is the case, you'll probably be tempted to avoid paying the overhead cost of a call to an empty function by declaring your destructor inline. That's a perfectly sensible strategy, but there's a twist you should know <NOBR>about.<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp25"></A><A NAME="222076"></A>Because your destructor is virtual, its address must be entered into the class's <CODE>vtbl</CODE> (see <A HREF="../MEC/MI24_FR.HTM#41284" TARGET="_top">Item M24</A>). But inline functions aren't supposed to exist as freestanding functions (that's what <CODE>inline</CODE> means, right?), so special measures must be taken to get addresses for them. <A HREF="./EI33_FR.HTM#6729" TARGET="_top">Item 33</A> tells the full story, but the bottom line is this: if you declare a virtual destructor <CODE>inline</CODE>, you're likely to avoid function call overhead when it's invoked, but your compiler will still have to generate an out-of-line copy of the function somewhere, <NOBR>too.<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI13_FR.HTM" TARGET="_top">Item 13: List members in an initialization list in the order in which they are declared.</A> <BR> Continue to <A HREF="./EI15_FR.HTM" TARGET="_top">Item 15: Have operator= return a reference to *this.</A></FONT></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -