首页 > 代码库 > 《Effective C++ 》学习笔记——条款07

《Effective C++ 》学习笔记——条款07

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************


二、Constructors,Destructors and Assignment Operators


Rule 07:Declare destructors virtual in polymorphic base classes

规则07:为多态基类声明virtual析构函数



1.一个简单的例子来引发“血案”

一个 表 的类:

class TimeKeeper  {
public:
    TimeKeeper();
    ~TimeKeeper();
    ...
};

然后根据用途,它作为基类,派生出 原子钟、水钟(滴漏)、腕表。

class AtomicClock : public TimeKeeper  { ... };    // 原子钟
class WaterClock : public TimeKeeper  { ... };    // 水钟
class WristWatch : public TimeKeeper  { ... };    // 腕表

这种做法非常合情合理,标准的例子,但是,许多人只想在程序中使用这个时间,而不关心它如何计算这些细节(这就是类呀~)这样,我们可以设计一个 factory(工厂)函数,返回指针指向一个计时对象。Factory函数就会返回一个基类指针,指向新生成的派生对象。

TimeKeeper* ptk = getTimeKeeper();<span style="white-space:pre">	</span>// 返回一个指针,TimeKeeper派生类的动态分配对象

为了遵守factory函数的规矩,被getTimerKeeper()返回的对象必须位于堆中。因此为了避免泄露内存和其他资源,将factory函数返回的每一个对象适当的delete掉很重要:

TimeKeeper* ptk = getTimeKeeper();
...
delete ptk;

上述的这些东西,都很正常,也很必要,但是有一点,后面的第13个条款也提到过:依赖客户执行delete动作,基本上便带有某种错误倾向。尽管,第18个条款谈到 factory 函数接口该如何修改以便防止常见的客户错误。

但是,在这个条款做的是,对付上面代码的根本弱点:(按照书上的话)纵使客户把每一件事都做对了,仍然没办法知道程序如何行动。


2.进入这个案件

问题在哪里呢?——在上面的getTimeKeeper返回的指针是指向一个派生类的对象,而那个对象是由一个基类指针删除,而目前的基类中有个non-virtual析构函数。

这里为什么出问题?(原因)—— C++明确的指出,当派生类的对象经由一个基类指针被删除,而该基类带一个non-virtual析构函数,其结果未有定义(简而言之,就是被派生的部分没有销毁)。

在通俗点,就是 基类部分被销毁了,但是派生类部分没有被销毁,这种局部销毁的现象会导致资源的浪费。

出了问题,当然要解决啦—— 解决这个方法也非常简单,就是给基类一个virtual函数。

class TimeKeeper  {
public:
    TimeKeeper();
    virtual ~TimeKeeper();
    ...
};
TimeKeeper* ptk = getTimeKeeper();
...
delete ptk;

这样就解决啦,这也体现了virtual的作用——允许派生类的实现得以客制化。

对了,还要注意一点——☆ 任何类只要带有 virtual 函数 都几乎确定应该也有一个 virtual 析构函数  ☆

这延伸出来就是,如果一个类没有virtual函数,说明这个类想成为一个基类,如果一个类不想成为base class 但仍给它安上一个virtual函数,这真的不是个好做法。


但是,即使一个类内 完全不带 virtual 函数,被non-virtual析构函数 绊倒的例子也有很多。

比如:

class SpecialString : public std::string  {
    ...
};

这个类继承了 string 类,但是string类内是 non-virtual 析构函数,所以被绊倒了吧~。~

所以,要注意所有不带virtual析构函数的类,比如 所有的STL容器(vector、list、set等等)

JAVA和C#就无须担心这些,因为Java有 final classes 而 C# 有 sealed classes 这种“禁止派生”的机制


3.还有其他什么关联案件吗?

来认识它——pure virtual——纯虚函数

这规则里,对于这个问题也提到了 pure virtual 析构函数,这种函数会导致 abstract class (抽象类)——就是不能实体化的类。

这样对于下面这种情况,就非常nice了:

——想让一个类单纯作为一个基类,但是没有可以声明为abstract class的 pure virtual函数(当然也可以单纯声明为一个类,不给它声明对象就行了,可是,万一手残一下,声明了个对象呢?总会留下安全隐患的)

这时候,你就可以声明一个 pure virtual 析构函数!

就像下面一样:

class AWOV  {
public:
    virtual ~AWOV() = 0;
};

这样,它有纯虚函数,所以是抽象类,不能定义实体,又因为是virtual 析构函数,所以不用担心 前面提到的析构函数的问题。

然后要为这个析构函数加个定义:

AWOV::~AWOV()  {  }

额,这里在插一句,析构函数的运作方式,类似于栈,先进后出,最深层 派生 的类,先析构,然后次级析构,最后才是基类的析构执行。


4.额...基类的virtual析构函数?

没错,基类也可以有virtual析构函数,这不是跟上面的冲突吗? 当然,这也是有条件的,给基类的virtual析构函数,这个规则只适用于 polymorphic(多态性质的) 基类中,上面的 TimeKeeper 就是多态性质的基类。


5.Please Rmember

★ polymorphic(多态性的)基类应该声明一个 virtual 析构函数。如果 class 有任何一个virtual函数,它就应该有一个virtual析构函数。

★ Classes 的设计目的如果不是作为 基类 使用,或不是为了具备多态性的,那就不该声明为virtual析构函数。




***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************

《Effective C++ 》学习笔记——条款07