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; 
}

打印如下

constructor of Base 
constructor of derived

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

#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; 
}

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; 
}

打印如下

constructor of base and init base_value and base_value is 100
call second constructor of Base_Value
---------------
destructor of Derived
destructor of Base

我们再看下面代码在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;

}

打印如下

base fun
derived fun
base destructor
base destructor

为什么会这样?我们注意我们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() {}
};

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              |
      |                              |

上图中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中指向的还是父类对应的虚函数,则最后编译器执行的是父类的函数而不是子类的函数.

你可能感兴趣的:(c/c++,c++,开发语言)