条款07: 为多态基类声明virtual析构函数

结论

  • 多态性质的基类声明一个虚析构函数,如果class带有任何虚函数,它就应该有一个virtual析构函数。
  • class的设计目的如果不是作为一个基类使用,或不是为了具备多态,就不该声明虚析构函数。

案例一:

设计一个TimeKeeper base class和derived class作为不同的计时方法,如下代码:

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

许多客户指向在程序中使用时间,不想操心时间如何计算细节,这时候我们可以设计factory(工厂)函数,返回指针指向一个计时对象。Factory函数会“返回一个base class指针,指向新生成的derived class对象”:

TimeKeeper* getTimeKeeper();		// 返回一个指针,指向一个TimerKeeper派生类的动态分配对象。

为遵守factory函数的规矩,被getTimeKeeper()返回的对象必须位于heap。因此为了避免泄漏内存和其它资源,将factory函数返回的每一个对象释放地delete掉很重要:

TimeKeeper* ptk = getTimeKeeper();	// 从TimeKeeper继承体系获得一个动态分配对象
...									// 运用它
delete ptk;							//  释放它,避免资源泄漏

问题
getTimeKeeper返回的指针指向一个derived class对象,而那个对象却经由一个base class指针被删除,而目前base class有个non-virtual析构函数。这将引来灾难。因此C++明确指出,当derived class对象经由一个base class指针被删除,而改base class带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的derived成分没有被销毁

解决方法
给base class一个虚析构函数。

class TimeKeeper
{
public:
	TimeKeeper();
	virtual ~TimeKeeper();
	...
};
TimeKeeper* ptk = getTimeKeeper();
,,,
delete ptk;					// 现在行为正确

topic1: 不含virtual函数的class,通常不令析构函数为虚函数

如果class不含virtual函数,通常表示它并不做一个base class。当class不企图被当作base class,令其析构函数为virtual往往是一个馊主意。主要是内存考虑。

案例1

下面是一个用来表示而为空间坐标的class:

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

如果int占用32 bits,那么Point对象占用64 bits。如果Point的析构函数为virtual,则会变得不同。

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

virtual函数的实现细节不重要。重要的是,如果Point Class内含virtual函数,其对象的体积会增加:在32-bite计算机体系结构中将占用64~96 bits(2个int加上vptr);在64-bit计算机体系结构中可能占64~128bits,因为指针在这样的计算机结构中占64bits。因此,为Point添加一个vptr会增加其对象大小达50%~100%。Point不再能塞入一个64-bit缓存起,而C++的Point对象也不再和其它语言(如C)内的相同声明有一样的机构(因为其它语言的对应物中并没有vptr),因此也就不再可能把它传递至其它语言所写的函数,除非你明确补偿vptr——那属于实现细节,也因此不再具有移植性。

因此,无端地将所有class的析构函数声明为virtual,就像从未声明它们为virtual一样,都是错误的。许多人的心得是:只有当class内含至少一个virtual函数,才声明virtual析构函数。、

topic2: 不要选择non-virtual析构函数的class作为基类

即使class完全不带virtual函数,被“non-virtual析构函数问题”给咬伤还是有可能的。举个例子,标准string不含任何virtual函数,但有时候程序员会错误地把它当作base class。

class SpecialString : public std::string	// 馊主意!std::string有个
{...};										// non-virtual析构函数

乍看无害,但如果你在程序任意某处无意间将一个pointer-to-specialString转换为一个pointer-to-string,然后转换所得的那个string指针delete掉,你立刻被流放到“行为不明确”的恶地上:

SpecialString* pss = new SpecialString("Impending Doom");
std::string* ps;
...
ps = pss;
...
delete ps;		// 未定义行为!现实中*ps的SpecialString资源会泄漏,
				// 因为SpecialString析构函数没被调用

topic3: virtual returnVlaue XXX() = 0;纯虚函数的使用场景

纯虚函数定义格式

virtual 返回值类型 函数名(函数参数)=0;

声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值