• C++ virtual destructor


    what happen when a derived class constructor is called?

    我们先了解derived class在调用constructor的时候,首先先调用的是其父类的构造函数
    why?
    首先我们的derived class obj中有所有的父类class的成员,在derived clas obj中其父类成员的可访问程度根据父类成员所在位置所定(子类对象可以访问父类的public和protected成员),所以在我们的子类构造的时候要先构造父类class(主要将其父类成员初始化)

    没有构造函数编译器会默认分配一个编译器给你,其功能仅仅是初始化成员而已

    如下图、

    #include 
    
    class Base{
    public:
        Base(){ std::cout<< "constructor of Base " <<'\n'; }
    };
    
    class Derived: public Base{
    public:
        Derived(){ std::cout << "constructor of derived " << '\n'; }
    };
    
    int main(){
        Derived* d1 = new Derived();
        return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    打印如下

    constructor of Base 
    constructor of derived
    
    • 1
    • 2

    当然我们构造子类的时候也可以选择性的调用父类构造函数

    #include 
    
    class Base{
    public:
        Base(){ std::cout<< "constructor of Base " <<'\n'; }
        Base(int a) : Base_value(a) {std::cout << "constructor of base and init base_value and base_value is " << Base_value << '\n';}
        int Base_value;
    };
    
    class Derived: public Base{
    public:
        Derived(): Base(){ std::cout << "constructor of derived " << '\n'; }
        Derived(int a): Base(a){std::cout << "call second constructor of Base_Value" << '\n';}
    };
    
    int main(){
        Derived* d1 = new Derived(100);
       // Derived obj;
        return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Class继承中的析构函数

    我们上面讲到了子类中其实是有父类的成员,所以在构造子类的时候先调用父类的构造函数构造父类,有构造那么就有析构函数,析构的顺序正好相反,先析构子类,再析构父类如下

    #include 
    
    class Base{
    public:
        Base(){ std::cout<< "constructor of Base " <<'\n'; }
        Base(int a) : Base_value(a) {std::cout << "constructor of base and init base_value and base_value is " << Base_value << '\n';}
        ~Base(){std::cout << "destructor of Base" <<'\n';}
        int Base_value;
    };
    
    class Derived: public Base{
    public:
        Derived(): Base(){ std::cout << "constructor of derived " << '\n'; }
        Derived(int a): Base(a){std::cout << "call second constructor of Base_Value" << '\n';}
        ~Derived(){std::cout << "destructor of Derived" << '\n';}
    };
    
    int main(){
        Derived* d1 = new Derived(100);
       // Derived obj;
       std::cout << "---------------" << '\n';
       delete d1;
       return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打印如下

    constructor of base and init base_value and base_value is 100
    call second constructor of Base_Value
    ---------------
    destructor of Derived
    destructor of Base
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们再看下面代码在delete derived class的时候只有base的destructor被执行,而derived class的destructor并没有被执行

    #include 
    
    class Base{
    public:
        virtual void fun() {std::cout << "base fun" << std::endl; }
        ~Base() {std::cout << "base destructor" << std::endl;}
    };
    
    class Derive: public Base{
    public:
        virtual void fun() { std::cout << "derived fun" << '\n';}
        ~Derive() { std::cout << "Derive destructor" <<'\n'; }
    };
    
    int main(){
        Base *b1 = new Base();
        Base *b2 = new Derive();
    
        b1->fun();
        b2->fun();
    
        delete b1;
        delete b2;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    打印如下

    base fun
    derived fun
    base destructor
    base destructor
    
    • 1
    • 2
    • 3
    • 4

    为什么会这样?我们注意我们new完Derived class后将其赋值给Base class指针,此时我们new完Derived class生成的obj1虽然有derived class自有的成员变量,但是obj1归根结底是Base class的obj,所以不可访问Derived Class的额外成员这叫做object slicing
    此时又有一个问题诞生,因为我们将子类的对象指针obj1赋值给父类,那么意味着obj1只能使用父类部分的成员,我们直接实例化父类这样实例化后的对象又比obj1占用的空间小,又简单不是挺好吗?为什么还要将子类对象obj1赋值给父类?这里有一个用处,就是父类中的一些成员是virtual,那么子类对象中父类vptr指向的函数为子类override后的地址,如下图

    class X {
        int     x;
        string str;
    
    public:
        X() {}
        virtual ~X() {}
    
        virtual void printAll() {}
    };
    
    class Y : public X {
        int     y;
    
    public:
        Y() {}
        ~Y() {}
    
        void printAll() {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Y memory layout

          |                              |          
          |------------------------------| <------ Y class object memory layout
          |          int X::x            |
    stack |------------------------------|
      |   |              int string::len |
      |   |string X::str ----------------|
      |   |            char* string::str |         
     \|/  |------------------------------|      |-------|--------------------------|
          |           X::_vptr           |------|       |       type_info Y        |
          |------------------------------|              |--------------------------|
          |          int Y::y            |              |    address of Y::~Y()    |
          |------------------------------|              |--------------------------|
          |               o              |              | address of Y::printAll() |
          |               o              |              |--------------------------|
          |               o              |              
    ------|------------------------------|--------
          |           X::X()             | 
          |------------------------------|       |   
          |           X::~X()            |       |
          |------------------------------|       | 
          |         X::printAll()        |      \|/ 
          |------------------------------|  text segment
          |           Y::Y()             |
          |------------------------------|
          |           Y::~Y()            |
          |------------------------------|
          |         Y::printAll()        |
          |------------------------------|
          |      string::string()        |
          |------------------------------|
          |      string::~string()       |
          |------------------------------|
          |      string::length()        |
          |------------------------------|
          |               o              |
          |               o              |
          |               o              |
          |                              |
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上图中Y继承了X,那么Y对象中应该含有X的vptr,上图中Y的确含有X的vptr,但是X的vptr不再指向X的函数而是指向class Y override后的函数
    所以我们在析构函数中加上virtual关键字后子类对象memory layout中对应的父类vptr指针(子类没有定义virtual函数,所以子类memory layout中没有关于子类的vptr指针,如果子类对象obj1赋值给父类,那么obj1就会使用其内存中x的vptr指向的虚函数)指向的析构函数变成了子类override的那一个(父类对象memory layout中vptr还是父类virtual function的地址),相反如果我们的子类中没有对父类的虚函数重载定义,那么子类中的vptr指向的是父类函数的地址,然后编译器执行的时候会先在vptr中找对应的函数地址,因为我们子类没有重新定义父类虚函数,那么vptr中指向的还是父类对应的虚函数,则最后编译器执行的是父类的函数而不是子类的函数.

  • 相关阅读:
    【NLP】第 6 章:XGBoost 超参数
    TVP 专家谈腾讯云 Cloud Studio:开启云端开发新篇章
    Glide加载https图片时 忽略证书校验
    电力系统通信与网络技术/智能变电站个人总结
    2022 - 8 洛谷
    高等数学(第七版)同济大学 习题5-1 个人解答
    Spring是什么?
    新项目,不妨采用这种架构分层,很优雅!
    TensorFlow 的基本概念和使用场景
    1、ArrayList源码解析
  • 原文地址:https://blog.csdn.net/qq_37026934/article/details/128034528