• 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++就不会调用析构函数链。

     



  • 相关阅读:
    MXNet中bucket机制注记
    MXNet中LSTM例子注记
    Bellman update中Value Iteration收敛证明
    关于分类问题中的激活函数特性影响
    mxnet包含NDArray的列表更新
    FeatureMap Viewer以及中心化对统计分布变化的影响讨论
    [ufldl]Supervised Neural Networks
    【ufldl tutorial】Convolution and Pooling
    【ufldl tutorial】Softmax Regression
    【leetcode刷题笔记】Rotate Image
  • 原文地址:https://www.cnblogs.com/lomper/p/4096118.html
Copyright © 2020-2023  润新知