我们先了解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;
}
我们上面讲到了子类中其实是有父类的成员,所以在构造子类的时候先调用父类的构造函数构造父类,有构造那么就有析构函数,析构的顺序正好相反,先析构子类,再析构父类如下
#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中指向的还是父类对应的虚函数,则最后编译器执行的是父类的函数而不是子类的函数.