zoukankan      html  css  js  c++  java
  • C++学习之虚析构函数

    什么样的情况下才需要虚析构函数?

    类需要控制自己的对象执行一系列操作时发生什么样的行为,这些操作包括:创建(对象)、拷贝、移动、赋值和销毁。在继承体系中,如果一个类(基类或其派生的类)没有定义拷贝控制操作,则编译器将自动的为其合成一个。即为合成的拷贝控制

    基类拷贝控制中,由于继承关系导致的最大影响就是:基类通常应该定义一个‘虚析构函数’。用以动态的分配继承体系中的对象。

    如:类A,B,C,D有如下继承关系(代码1):

    1
    2
    3
    4
    class A;
    class B:public A;
    class C:public B;
    class D:public C;

    其中:类A定义如下(代码2):

    1
    2
    3
    4
    5
    class A {
    public:
        //其他函数
        virtual ~A()=default;//用于动态绑定的析构函数

    };

    当我们delete一个A* item 类型的指针时,该指针可能是指向A的,也可能指向的是B,C,D中的一个,编译器在delete时必须弄清楚到底应该执行A,B,C,D中哪一个类的析构函数。此时需要编译器进行动态绑定(即只有运行时才能知道到底item 指向的是那个类)。当在基类A中定义的析构函数为虚析构函数时,无论A的派生类(B,C,D)使用的是合成的析构函数还是自己定义的析构函数,它们都是虚析构函数。说人话就是:你老祖姓虚,传到你还是姓虚,你儿子孙子都得姓虚(千万别较真女生~~~),不管这儿孙是你血缘的还是你自己领养的,都得虚!

    举个例子(代码3):

    1
    2
    3
    4
    A *item = new A;  //此时item指向的就是A,静态类型于动态类型一致(这就是你本人)
    delete item;  //调用A自己的析构函数(自杀了,杀的是你自己)
    item = new B;  //静态类型为A,动态类型为B(此时你的血脉传到了你儿子身上,item是你儿子了!)
    delete item;   //调用B自己的析构函数(你儿子要自杀,此时死的是你儿子,和你无关)

    如果基类A的析构函数不是虚的(虚函数),则delete时,如果item指向的不是A,而是B或其他A的派生类,则会产生未定义的行为,未定义的行为通常会导致BUG。

    那么问题来了:什么样的情况下才需要虚析构函数呢?是所有类都应该有吗?

    通过基类的指针删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

    简单解释一下,派生类B中所有的属性以操作(Bp)不仅有B自己定义的属性、操作(Bself),还有继承自A的属性、操作(Aself),即Bp=Bself+Aself;

    如代码3,当delete一个指向B的item时(其实item的类类型为A),如果A中的析构函数不是虚的,则只会删除Aself部分,因为item的类类型其实是A,只是指向了其派生类对象。但是在A的析构函数里其实并没有Bself部分,那这部分就删不掉了--这就是所谓的内存泄漏!只有A的析构函数是虚的,才能删除的不仅有Aself,还有Bself,即Bp全部被删除了。这才是正确的。

    同时,并不是所有类都需要将析构函数定义成虚的。因为编译器在编译时会给类添加一个虚函数表,里面来存放虚函数指针,如果都定义成虚的,这样就会增加类的存储空间。浪费了!不用作基类,也不需要为虚的!不需要通过基类的指针操作派生类的对象时,基类的析构函数应该是虚的。

    这里借用一下文章代码:什么时候要用虚析构函数? 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class ClxBase
    {
    public:
         ClxBase() {};
         virtual ~ClxBase() {};
         virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
    };
    class ClxDerived : public ClxBase
    {
    public:
         ClxDerived() {};
         ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
         void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
    };
         代码
    ClxBase *pTest = new ClxDerived;
    pTest->DoSomething();
    delete pTest;

    正常情况应该输出:

    1
    2
    Do something in class ClxDerived!
    Output from the destructor of class ClxDerived!

    如果将类ClxBase的析构函数定义为非虚(去掉前面的那个virtual),则输出为:

    1
    Do something in class ClxDerived!

    根本没有调用ClxDerived的析构函数哦~~~

    同样,在什么时候要用虚析构函数? 中,提出了一个这样的问题:

    为什么继承一个没有虚析构函数的类是危险的?

    这个问题吗其实上面已经解释过了,会导致删不完!内存泄漏问题。当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你delete一个这样的类时,C++就不会调用析构函数链。

     



  • 相关阅读:
    android 中文 api (43) —— Chronometer
    SVN客户端清除密码
    Android 中文 API (35) —— ImageSwitcher
    Android 中文API (46) —— SimpleAdapter
    Android 中文 API (28) —— CheckedTextView
    Android 中文 API (36) —— Toast
    Android 中文 API (29) —— CompoundButton
    android 中文 API (41) —— RatingBar.OnRatingBarChangeListener
    Android 中文 API (30) —— CompoundButton.OnCheckedChangeListener
    Android 中文 API (24) —— MultiAutoCompleteTextView.CommaTokenizer
  • 原文地址:https://www.cnblogs.com/lomper/p/4096118.html
Copyright © 2011-2022 走看看