📄 wwwtc4answer.htm
字号:
<P>
Because the container holds <TT>Base</TT> pointers, the container takes on a polymorphic form. It can hold <TT>Base</TT>
objects, or derived objects. It really doesn't care. Because <TT>DoSomething</TT> is virtual, the correct member function
gets called. But what about the destruction of the objects? Because the <TT>Base</TT> destructor is not virtual, the derived
destructors will never run. If you design a base class and you don't make the destructor virtual, you are essentially preventing
users of your class from ever putting objects of your class into a container. Ouch!
</P>
<P>
I have also heard some people argue that if the derived class does not allocate any additional memory by calling operator <TT>new</TT>,
that there is no need for its destructor to be called. Hence, the base destructor need not be virtual. This is complete hogwash. Look back
at the original code for this article. The <TT>Derived</TT> class contained two <TT>Foo</TT> objects. One of them was a pointer, and one
of them was not. Neither of them were destroyed properly. It makes no difference whether or not they are pointers. The reason for this is
clear. When you write the <TT>Derived</TT> destructor, the compiler silently inserts code to destroy all non-pointer member objects. You can step
through this code in assembler if you want. This code is part of the <TT>Derived</TT> destructor. If the <TT>Derived</TT> destructor
is not called, neither is the silent code that cleans up non-pointer objects.
</P>
<p>
Ok, ok, but what if the <TT>Derived</TT> object does not contain any member objects? Then its OK not to have a virtual destructor
in the base class, right? Well, maybe. But if you are designing a base class, do you have any right to prevent all derived classes
from having any member variables (other than integral types, such as int). People are not going to want to use your class as a base
if you try to force that rule on them.
</P>
<P>
There is only one situation where it is acceptable for a base class destructor to not be virtual: the class is a concrete class and it is
should not be derived from. In reality, its not a base class at all. No classes derive from it.
</P>
<P>
<B>Note :</B> The example code for this article contained a class hierarchy that was only two levels deep. When the hierarchy is more than
two classes deep, the deepest base class should declare a virtual destructor (ie, the one at the top). For example:
</P>
<pre>
<b>class</b> Base
<b>{</b>
<b>...</b>
<b>public</b><b>:</b>
<b>virtual</b> <b>~</b>Base<b>(</b><b>)</b><b>;</b>
<b>}</b><b>;</b>
<b>class</b> Middle <b>:</b> <b>public</b> Base
<b>{</b>
<b>...</b>
<b>public</b><b>:</b>
<b>~</b>Middle<b>(</b><b>)</b><b>;</b>
<b>}</b><b>;</b>
<b>class</b> End <b>:</b> <b>public</b> Middle
<b>{</b>
<b>...</b>
<b>public</b><b>:</b>
<b>~</b>End<b>(</b><b>)</b><b>;</b>
<b>}</b><b>;</b>
</pre>
<P>
Recall that once a function is declared as virtual in a base class, it is considered virtual in all derived classes. This is why the
<TT>Middle</TT> and <TT>End</TT> classes do not have to list virtual before their destructors. Some people like to list virtual anyway,
to avoid confusion.
</P>
<hr size = 1>
<P>
<B>Note :</B> Chris Uzdavinis sent me this note:
</P>
<P>
<I>I read the new "w3tc" article, on virtual destructors in the base class of
polymorphic hierarchies. You explain it well, but I think that your warning
is too soft. :) The ANSI standard says that if you call delete on a pointer
that points to a derived type, then the destructor SHALL be virtual or
behavior is undefined. You cannot even count on the base part being deleted
correctly.</I>
</P>
<P>
<I>See Ansi 5.3.5 p3 for details.</I>
</P>
<P>
<I>
Chris Uzdavinis
</I>
</P>
<hr size = 1>
<P>
<B>Note :</B> If you do not provide a destructor for your base class, the compiler will generate a default one for you.
This default destructor is not virtual. Omitting a destructor in the base class is equivalent to adding a non-virtual destructor.
In order to follow the advice in this article, you may have to add empty, virtual destructors to your base classes. In fact,
this is how many people fall victim to this problem. They forget to provide a destructor in a base class that does not contain
any members.
</P>
<hr size = 1>
<P>
<B>Note :</B> This is also a problem in Object Pascal and Delphi. Well sort of. The same problem exists, but Borland's version of
Object Pascal essentially squashes it before it has a chance to surface. In Object Pascal, all classes are derived from <TT>TObject</TT>.
It does not matter whether you list <TT>TObject</TT> as a base class or not. The <TT>TObject</TT> class contains a virtual
destructor, which prevents the problem from surfacing. If Borland ever changes Delphi so that classes are not automatically derived from
<TT>TObject</TT>, then the problem would become more serious for Object Pascal programmers.
</P>
<hr size = 1>
<P>
<B>Note :</B> If you are thinking of deriving from a class that someone else wrote, check to make sure that it has a virtual
destructor. If it does not, then you can't derive from it. The STL classes fall into this category. Their destructors are
not virtual, so you may not want to derive from them.
</P>
<hr size = 1>
<P>
<B>Note :</B> Scott Meyers discusses the importance of making base class destructors virtual in his book
<A TARGET=_top HREF="http://www.amazon.com/exec/obidos/ISBN=0201924889/cbuilderfoundatiA/">"Effective C++"</A>.
He discusses something in his book that I thought was worth mentioning here. While it is important to make the destructor
of a base class virtual, it is equally important that you don't overdo it. If a class will not and should not be used as a
base class, then do not make the destructor virtual. In fact, if the class is not a suitable base class, then it should not
contain any virtual methods.
</P>
<P>
Meyers gives some good examples of classes that should not have a virtual destructor. One example was a point class, similar to the
<TT>TPoint</TT> class in C++Builder. <TT>TPoint</TT> represents an x-y point in a Cartesian coordinate system. The declaration
for <TT>TPoint</TT> is in windows.hpp
</P>
<pre>
<b>struct</b> TPoint
<b>{</b>
TPoint<b>(</b><b>)</b> <b>{</b><b>}</b>
TPoint<b>(</b><b>int</b> _x<b>,</b> <b>int</b> _y<b>)</b> <b>:</b> x<b>(</b>_x<b>)</b><b>,</b> y<b>(</b>_y<b>)</b> <b>{</b><b>}</b>
TPoint<b>(</b>POINT<b>&</b> pt<b>)</b>
<b>{</b>
x <b>=</b> pt<b>.</b>x<b>;</b>
y <b>=</b> pt<b>.</b>y<b>;</b>
<b>}</b>
<font color="navy">// ...</font>
<b>int</b> x<b>;</b>
<b>int</b> y<b>;</b>
<b>}</b><b>;</b>
</pre>
<P>
This version of <TT>TPoint</TT> contains two integer member variables. Since the structure does not contain any virtual functions, the
class won't have a vtable. The absence of a vtable means that the total size of a <TT>TPoint</TT> object will equal 8 bytes, 4 bytes
for each integer member variable. If we add a virtual destructor to <TT>TPoint</TT>, then the size of the class grows by four bytes,
making the total size 12. In addition to the extra four bytes, the code size of the class also grows because the compiler has to insert
extra code to initialize and cleanup the vtable.
</P>
<P>
The extra four bytes for the vtable pointer is uneeded bloat if the class does not serve as a base class. In "Effective C++", Meyers
advice is simple: if a class serves as a base class, then it should have a virtual destructor. If a class functions as a stand alone,
concrete class, such as <TT>TPoint</TT>, then it should not have virtual destructor. Furthermore, if someone gives you class that
does not have a virtual destructor, then it means that either the class should not be derived from, or that person has not read "Effective
C++". Either way, you probably should not derive from that class. (PS- If you don't already own a copy of
<A TARGET=_top HREF="http://www.amazon.com/exec/obidos/ISBN=0201924889/cbuilderfoundatiA/">"Effective C++"</A>, you should buy one. This
book is well worth the money).
</P>
</TD> </TR>
</TABLE>
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -