effect C++ 为多态基类声明virtual 析构函数

virtual 析构函数

记录时间,设计TimeKeeper base class 和derived classes 

class TimeKeeper{
public:
 TimeKeeper();
 ~TimeKeeper();
 ....
};
class AtomicClock :public TimeKeeper{...}  //原子钟
class WaterClock: public TimeKeeper{...}   //水钟
class WristClock: public TimeKeeper{...}   //腕表
设计factor函数,返回指针指向一个计时对象。Factory 函数会“返回一个base class 指针,指向新生成之derived class 对象”

TimeKeeper * getTimeKeeper(); //返回一个指针,指向一个TimeKeeper派生类的动态分配对象
为遵循factory函数的规矩,被getTimeKeeper()返回的对象必须位于heap。因此为了避免泄露内存和其他资源,将factory函数返回的每一个对象适当地delete:


TimeKeeper *ptk=getTimeKeeper(); //获得一个动态分配的对象
.... 
delete ptk;                      //释放

getTimeKeeper 返回的指针指向一个derived class 对象 (例如AtomoticClock), 而那个对象却经由一个base class 指针 (例如一个TimeKeeper*指针)被删除,而目前的base class(TimeKeeper)有一个non-virtual析构函数。C++指出,当drived class对象经由一个base 指针被删除,而该base class 带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的derived 成分没有被销毁。如果getTimeKeeper返回指针指向一个AtomicClock对象,其内的AtomicClock成分(也就是声明于AtomicClock class 内的成员变量)很可能被销毁,而AtomicClock的析构函数也未能执行起来。然而其 base class 成分(也就是TimeKeeper这一部分)通常会被销毁,于是造成一个诡异的“局部销毁”对象。


解决:给base class 一个virtual 析构函数,此后删除derived class 对象就会销毁整个对象。

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

如果class 不含virtual函数,通常表示它并不意图被用一个base class。

class Point{
public:
Point(int xCoord,int yCoord);
~Point();
private:
int x,y;
}

如果int 占32bits,那么Point 对象可塞入一个64-bit缓存器中。可以被当做一个“64-bits量”传递给其他语言(C)撰写的函数

欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数被调用。这份信息通常是由一个所vptr(virtual table pointer)指针指出。vptr 指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个待遇virtual 函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。


virtual 函数的实现细节并不重要。重要的是如果Point class内含virtual函数,其对象的体积会增加:在32-bit计算机体系结构中将占用64bits至96bits(两个ints加上vptr)。为Point添加一个vptr会增加对象大小达到50%~100%,而C++的Point对象也不能和其他语言内声明有着相同的结构(因为其他语言对应物并没有vptr,除非明确补偿vptr)。不再具有移植性。

无端地将所有classes的析构函数声明为virtual是错误的。


即使class完全不带virtual函数,被“non-virtual析构函数问题”给咬伤还是有可能的。

标准string不含任何virtual 函数,如果将一个pointer-to-SecialString转换成一个pointer-to-string,然后将转换所得的那个string 指针delete调调。

class SpecialString :public std::string{ std::string 有个non-virtual析构函数
...
};
SpecialString *pss = new SpecialString("Impending Dom");
std::string*ps; 
...
ps=pss;   //SpecialString* => std:string;
...
delete ps; //未有定义!现实中的*ps的SpecialString 资源会泄露,因为SpecialString 析构函数没有被调用
任何不带virtual析构函数的class,包括所有STL容器。不能继承。(C++没有提供类似java的final classes 或C# 的sealed classed 那样的“禁止派生”机制)

pure virtual 

令class带一个pure virtual 析构函数导致abstract classes ——也就是不能被实体化的class。也就是不能为那种类型创建对象。然而有时候希望拥有抽象class,但手上没有任何pure virtual函数。由于抽象class总是企图被当作一个base classes 来用,而又由于base class应该有个virtual 析构函数,并且由于pure virtual 函数会导致抽象class。 因此 , 为希望它成为抽象的那个class声明一个pure virtual析构函数。

class AWOV{
public:
   virtual ~AWOV()=0;
}
这个class有一个pure virtual 函数,所以它是个抽象class,又由于它有个virtual 析构函数,所以你不需要担心析构函数的问题。为这个pure virtual 析构函数提供一份定义:

AWOV::~AWOV(){} //pure virtual 析构函数的定义


析构函数的运作方式是,最深层派生的那个class 其析构函数最先被调用,然后是其每一个base class的析构函数被调用。编译器会在AWOV 的derived classes 的析构函数中创建一个对~AWOE的调用动作,所以你必须为这个函数提供一份定义。如果不这样做。连接器会发出抱怨。

“给base classes 一个virtual 析构函数”,这个规则只适用于polymorphic(带多态性质的)base classes 身上。这种base classes的设计目的是为了用来“通过base class接口处理derived class”。TimeKeeper 就是一个polymorphic base class ,因为我们希望处理 AtomicClock和WaterClock对象,纵使我们只有TimeKeeper指针指向它们。


polumorphic base class 应该声明一个virtual 析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual 析构函数。

Classes 的设计目的如果不是作为base classes 使用,或不是为了具备多态性,就不该声明virtual函数。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值