《Effective C++》学习笔记(条款07:为多态基类声明virtual析构函数)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

1 多态的基类需要虚构函数

 class TimeKeeper{                              //计时器类,用来当做基类
 public:
     TimeKeeper();                              //这是构造函数
     ~TimeKeeper();                             //这是析构函数
   ......
 };
 
 class AtomicClock : public TimeKeeper{...};   //原子钟是一种计时器
 class WaterClock : public TimeKeeper{...};    //水钟也是一种计时器
 
 TimeKeeper* getTimeKeeper(){...}              //用来返回一个动态分配的基类对象,工厂类
 TimeKeeper* ptk = getTimeKeeper();            //使用这个基类指针操作它的子类,假设这个子类是WaterClock
 .....                                         
 delete ptk;                                   //使用完毕,释放资源

上述代码的基类没有虚析构函数,直接 delete 基类指针指向的派生类(delete ptk;),会导致派生类 WaterClock 中的基类成分( 即 TimeKeeper 的成员变量)被释放,而专属于派生类 WaterClock 内的成员变量没被释放,且它的析构函数也未能执行,于是造成了一个诡异的局部销毁对象,这也是造成内存泄漏、败坏数据结构、在调试器上浪费时间的原因之一。

解决方法:给多态的基类的析构函数添加 virtual 关键字

 class TimeKeeper{           //计时器类,用来当做基类
 public:
     TimeKeeper();          	//这是构造函数
     virtual ~TimeKeeper();	//这是虚析构函数
   ......
 };

此后 delete 基类指针指向的派生类,派生类中的所有成员变量都会被释放。

2 虚函数的工作原理

虚函数是用来在运行时(runtime),自动把编译时未知的对象,比如用户输入的对象,和它所对应的函数绑定起来并调用。当一个类中有虚函数时,编译器会给这个类添加一个隐藏变量 vptr,即虚函数表指针(virtual table pointer),vptr 指向一个由函数指针构成的数组 vtbl,即虚函数表(virtual table)。当对象调用某一虚函数时,具体调用哪个函数取决于该对象的 vptr 所指的那个 vtbl——编译器在其中寻找适当的函数指针。

如果类内有虚函数,其对象的体积会增加,因为 vptrvtbl都需要占用空间的,所有不要无谓声明虚析构函数。

3 不要继承标准库中的类

 class SpecialString : public std::string{...};      //某个继承自标准字符串的类
 
 SpecialString* pss = new SpecialString("Hi");
 std::string* ps;
 ...
 ps = pss;
 delete ps;                                          //使用完后从基类删除内存

这样的写法会导致内存泄漏,因为标准库的字符串并没有把析构函数定义为虚函数,它们并不是用来拿去继承的,所以不能随便继承,包括 STL。虽然C++不像Java有final和C#有 sealed 来阻止某些类被继承的机制,我们也要拒绝这种写法。

4 抽象类

抽象类是包含至少一个纯虚函数的类,而且它们不能被实例化,只能通过指针来操作,是纯粹被用来当做多态的基类的。

因为多态的基类需要有虚析构函数抽象类又需要有纯虚函数,那么在抽象类中就要把析构函数声明为纯虚函数,如下述代码

 class AWOV{
   public:
     virtual ~AWOV() =0; //"=0"只是一个关键字,用来声明纯虚函数,并不把任何东西设为0
 };
 /*
 析构函数的调用顺序:首先是调用派生类的析构函数,然后是其每一个基类的析构函数被调用,因此作为析构函数调用的终点,要保证有一个定义,否则链接器会报错。
 */
 AWOV::~AWOV(){
     //纯虚析构函数的定义
 }

Note

  • 带多态性质的基类应该声明一个虚析构函数;如果 class 内至少有一个虚函数,应该为它声明虚析构函数
  • 不是所有基类都需要声明虚析构函数的,如条款06中的 Uncopyable 类,只是为了实现一个功能,而不具有多态性质。

条款08:别让异常逃离析构函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果基类析构函数不是虚函数,它不会被其导出类的析构函数覆盖,导致在析构多态对象时不会调用导出类的析构函数。这可能会导致资源泄漏或其他错误。 举个例子,假设我们有一个基类 Shape 和它的导出类 Circle。如果 Shape 的析构函数不是虚函数,那么当我们删除一个 Circle 类型的对象时,它只会调用 Shape 的析构函数,而不是 Circle 的析构函数,因此 Circle 可能分配的任何资源都不会被正确释放。 因此,如果您有一个多态继承结构,请确保基类析构函数是虚函数。 ### 回答2: 基类析构函数不是虚函数,会带来以下几个问题: 1. 对象的内存泄露:当基类指针指向派生类对象时,如果基类析构函数不是虚函数,那么当使用delete操作符删除这个基类指针时,只会调用基类析构函数,而不会调用派生类的析构函数。这样会导致派生类对象中的资源没有被正确释放,造成内存泄漏。 2. 对象切割:在基类指针指向派生类对象的情况下,如果基类析构函数不是虚函数,那么当使用基类指针删除对象时,只会调用基类析构函数,而不会调用派生类的析构函数。这样会导致对象的派生类部分没有得到正确的析构,可能导致资源泄露或行为异常。 3. 引发未定义行为:如果基类析构函数不是虚函数,且派生类有带有资源占用的成员变量时,当使用基类指针删除派生类对象时,派生类的析构函数将不会被调用。这样会导致派生类对象中的资源没有得到正确释放,可能造成程序行为的未定义。 综上所述,基类析构函数不是虚函数会导致对象的内存泄露、对象切割、以及引发未定义行为。为了避免这些问题,应该将基类析构函数声明为虚函数,以确保在删除对象时能够正确调用派生类的析构函数,从而正确释放资源。 ### 回答3: 基类析构函数不是虚函数会导致多态中的对象销毁时不会调用派生类的析构函数,而只会调用基类析构函数。这可能会引发以下问题: 1. 对象不完全销毁:如果基类析构函数不是虚函数,则当我们使用基类指针指向派生类对象时,通过基类指针删除该对象时,只会调用基类析构函数,而不会调用派生类的析构函数。这将导致派生类对象的资源无法正确释放,可能会导致内存泄漏或资源泄漏。 2. 数据丢失:如果基类指针指向派生类对象,并且我们将该指针赋值给派生类对象的另一个派生类指针,然后使用该派生类指针删除对象,由于基类析构函数非虚,只会调用基类析构函数,不会调用派生类的析构函数。这将导致派生类对象的成员数据无法正确释放,可能会导致数据丢失或程序行为异常。 3. 无法正确释放资源:在多态中,我们通常通过基类指针来管理派生类对象,当需要释放基类指针所指向的对象时,如果基类析构函数不是虚函数,只会调用基类析构函数,而不会调用派生类的析构函数。这将导致派生类对象中的资源无法正确释放,可能会导致资源泄漏或导致程序异常。 因此,为了避免上述问题,通常在基类中的析构函数应该声明为虚函数,以确保派生类对象在销毁时能够正确调用派生类的析构函数,从而保证资源的正确释放和程序的正常运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值