ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-01-25 12:00:02  阅读:156  来源: 互联网

标签:TimeKeeper 函数 Effective 多态 virtual 析构 基类 指针 07


一、从一个例子中介绍为什么要为基类使用virtual析构函数

  • 我们创建一个TimeKeeper基类一些及其它的派生类作为不同的计时方法
class TimeKeeper
{
public:
    TimeKeeper() {}
    ~TimeKeeper() {}  //非virtual的
};
 
//都继承与TimeKeeper
class AtomicClock :public TimeKeeper{};
class WaterClock :public TimeKeeper {};
class WristWatch :public TimeKeeper {};
  • 如果客户想要在程序中使用时间,不想操作时间如何计算等细节,这时候我们可以设计factory(工厂)函数,让函数返回指针指向一个计时对象。该函数返回一个基类指针,这个基类指针是指向于派生类对象的
TimeKeeper* getTimeKeeper()
{
    //返回一个指针,指向一个TimeKeeper派生类的动态分配对象
}
  • 因为函数返回的对象存在于堆中,因此为了在不使用时我们需要使用释放该对象(delete)
TimeKeeper* ptk = getTimeKeeper();
 
delete ptk;
  • 此处基类的析构函数是非virtual的,因此通过一个基类指针删除派生类对象是错误的
  • 解决办法:将基类的析构函数改为virtual就正确了
    class TimeKeeper
    {
    public:
        TimeKeeper() {}
        virtual ~TimeKeeper() {}
    };
  • 声明为virtual之后,通过基类指针删除派生类对象就会释放整个对象(基类+派生类) 

二、为什么将析构函数声明为virtual就正确了?

如果不将基类的析构函数声明为virtual

  • 我们在一篇文章中说过(参阅:https://blog.csdn.net/qq_41453285/article/details/103106043),将基类指针/引用绑定于派生类对象身上,那么通过这个指针/引用操作对象,操作的内容与指针/引用的类型有关,因此此时通过基类的指针释放(delete)对象,那么调用的是基类的析构函数,此时派生的析构函数没有执行,相当于只释放了基类的内存,但是派生类的内存没有释放

如果将基类的析构函数声明为virtual

  • 我们在多态文章中说过(参阅:https://blog.csdn.net/qq_41453285/article/details/103108495),如果将基类指针/引用绑定于派生类,此时通过这个指针/引用调用虚函数,那么这个虚函数的调用与指针/引用所指向的类型有关,因此当通过基类的指针释放(delete)对象时,那么调用的是派生类的析构函数(我们知道析构函数的执行顺序是:先执行派生的析构函数-->然后再执行基类的析构函数,这样才能保证在整个继承体系中把所有的内存都是释放了),因此整个派生体系的内存都释放了,因此不会造成任何内存泄漏

三、何时使用virtual析构函数

  • 如何使用virtual析构函数也是分场景的,下面分析一些场景

①继承体系中:含有virtual函数或要使用多态应该使用virtual虚析构函数

  • ①我们通常使用继承关系,就是希望在某些情况下使用“多态”。因此使用基类指针指向于派生类会常见的,因此在具有类继承的关系下,就应该为基类设计virtual析构函数虽然不是强制的,但是在使用基类指针释放子类对象时就会出错)
  • 如果基类中有虚函数,那么就强烈建议为基类设计virtual析构函数了,因此含有虚函数就说明有很大可能会用到多态(虽然也是建议,不是强制的)

②继承体系中:没有virtual函数/不使用多态可以不使用virtual虚析构函数

  • 与①介绍类似,如果你的基类被设计的时候明确:不会使用到多态,不会使用到任何virtulal函数。那么可以不为基类设计virtual虚析构函数

③没有继承关系:不要设计virtual虚析构函数

  • 如果类中有virtual,就一定会含有一个虚函数指针,因此在没有继承的关系中,使用virutal会导致对象大小增加,浪费内存
  • 另外一个原因:如果有virtual,那么C++的对象将不会与其他语言(如C语言)有着相同的结构(因为有了虚函数指针/虚函数表),因此就不能把这个对象传递给(或接受自)其他语言所编写的函数(丧失了兼容性)

四、继承于STL标准容器产生的错误

  • STL容器如vector、list、set,trl::unordered_map等等,这些容器都不带有虚析构函数,所以如果当你定义一个类继承于这些容器,然后再使用容器基类指针释放你自己定义的类对象,那么将会产生错误
  • C++没有类似于JAVA的final classes或C#的sealed classes那样的“禁止派生”机制,所以需要注意继承于STL容器产生的错
    //继承于string,string没有虚析构函数
    class SpecialString :public std::string{};
     
     
    SpecialString* pss = new SpecialString;
    std::string *ps = pss;
     
    delete ps; //错误

五、virtual析构函数在抽象类中的使用

  • 我们知道,如果一个类拥有纯虚函数(=0),那么该类是一种“抽象类”,并且自身不可以被实例化
  • 此处介绍一种virtual析构函数在类中的使用:有时候我们希望定义一个抽象类,但是抽象类必须要有纯虚函数,那么此时怎么办呢?此时我们可以将类的析构函数定义为纯虚函数
  • 如果通过抽象类指针释放派生类对象:那么抽象类的析构函数虚不仅要设为virtulal,还需要给出一份定义
//抽象类
class AWOV
{
public:
	virtual ~AWOV() = 0;
};
 
//需要给出一份定义
AWOV::~AWOV(){}
 
class A :public AWOV {};
 
 
AWOV* p = new A;
delete p;       //正确(因为AWOV也要释放,所以需要定义AWOV的析构函数)

六、总结

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

标签:TimeKeeper,函数,Effective,多态,virtual,析构,基类,指针,07
来源: https://blog.csdn.net/u012906122/article/details/113106916

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有