多态

概述

多态可以简答地概括为“一个接口,多种方法”,通常是指对于同一个消息,同一个调用,在不同地场合,不同的情况下执行不同的行为

c++支持两种多态性:编译时多态和运行时多态

编译时多态:也称为静态多态,我们之前学习过的函数重载运算符重载,模板就是采用的静态多态,C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,又称为静态联编。

运行时多态:在一些场合下,编译器无法在编译过程中完成联编,必须在程序运行时完成选择,因此编译器必须提供这么一套称为“动态联编”(dynamic binding)的机制,也叫动态多态。C++通过虚函数来实现动态联编。接下来,我们提到的多态,不做特殊说明,指的就是动态多态

虚函数

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
39
40
41
42
43
44
45
46
47
48
49
50
class Base{
public:
Base(long x)
: _base(x)
{}

void display() const{
cout << "Base::display()" << endl;
}

void func1(){}
private:
long _base;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{}

void display() const{
cout << "Derived::display()" << endl;
}

void func2(){};
private:
long _derived;

};

void print(Base * pbase){
pbase->display();
}

void test0(){
Derived d1(4,9);
Base *pbase = &d1;
pbase->display();//调用的是基类的display
pbase->func2();//error 基类指针指向派生类对象只能访问基类子对象的内容
pbase->func1();//success

Derived * pderived = &d1;//调用的是派生类的display


}
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
39
40
41
42
43
44
45
46
47
48
49
class Base{
public:
Base(long x)
: _base(x)
{}

virtual void display() const{
cout << "Base::display()" << endl;
}

void func1(){}
private:
long _base;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{}

void display() const{
cout << "Derived::display()" << endl;
}

void func2(){};
private:
long _derived;

};

void print(Base * pbase){
pbase->display();
}

void test0(){
Derived d1(4,9);
Base *pbase = &d1;
d1.dispaly();//调用的居然是派生类的display
cout << sizeof(Base) << endl;//16

Derived * pderived = &d1;//调用的是派生类的display


}

加入virtual关键字,Base存储空间就多存了一个虚函数指针,该虚函数指针指向一张虚函数表,其中存放的就是虚函数的入口地址

虚函数指针

image-20240618112036424

在派生类中又定义了同名的函数,发生了覆盖机制,覆盖的是虚函数表中虚函数的入口地址

image-20240618112231032

Base* p 去指向Derived对象,依然只能访问到基类的部分。用指针p去调用display函数,发现是一个虚函数,那么会通过vfptr找到虚表,此时虚表中存放的是Derived::display的入口地址,所以调用到Derived的display函数。

基类定义了一个虚函数,那么派生类中和该虚函数同名的成员函数也是虚函数,不管加不加virtual

虚函数的覆盖

返回类型不一致的情况

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
39
40
41
42
class Base{
public:
Base(long x)
: _base(x)
{}

virtual void display() const{
cout << "Base::display()" << endl;
}

void func1(){}
private:
long _base;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{}

int display() const{//error 既不能覆盖也不能重载
cout << "Derived::display()" << endl;
return 1;
}

void func2(){};
private:
long _derived;

};

void print(Base * pbase){
pbase->display();
}

void test0(){
}

参数不同的情况

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
39
40
41
42
43
44
class Base{
public:
Base(long x)
: _base(x)
{}

virtual void display() const{
cout << "Base::display()" << endl;
}

void func1(){}
private:
long _base;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{}

void display() {//参数不同也不能实现覆盖
cout << "Derived::display()" << endl;
return 1;
}

void func2(){};
private:
long _derived;

};

void print(Base * pbase){
pbase->display();
}
void test(){
Derived d1(4,9);
Base *pbase = &d1;
d1.dispaly();//调用的仍然是基类的 没有实现覆盖
}

覆盖的要求

  • 与基类的虚函数有相同的函数名;
  • 与基类的虚函数有相同的参数个数;
  • 与基类的虚函数有相同的参数类型;
  • 与基类的虚函数有相同的返回类型。

要求形式要完全一致

主动声明要求覆盖基类的虚函数 override关键字

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
39
class Base{
public:
Base(long x)
: _base(x)
{}

virtual void display() const{
cout << "Base::display()" << endl;
}

void func1(){}
private:
long _base;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{}
//override告诉编译器在此处定义的虚函数是要对基类的虚函数进行覆盖,覆盖要求函数的形式完全一致
void display() override{
cout << "Derived::display()" << endl;
return 1;
}

void func2(){};
private:
long _derived;

};

void print(Base * pbase){
pbase->display();
}

思考一下重载,隐藏和覆盖各是什么意思?

总结:

(1)覆盖是在虚函数之间的概念,需要派生类中定义的虚函数与基类中定义的虚函数的形式完全相同

(2)当基类中定义了虚函数时,派生类去进行覆盖,即使在派生类的同名的成员函数前不加virtual,依然是虚函数;

(3)发生在基类派生类之间,基类与派生类中同时定义相同的虚函数。覆盖的是虚函数表中的入口地址,并不是覆盖函数本身。

动态多态(虚函数机制)的触发条件

要求满足下面的全部要求

1.基类定义虚函数

2.派生类中要覆盖虚函数

3.创建派生类对象

4.基类的指针指向派生类的对象(或基类引用绑定派生类的对象)

5.通过基类指针(引用)调用虚函数

虚函数表

在虚函数机制中virtual关键字的含义:

1.虚函数是存在的(存在)

2.通过间接的方式去访问。(间接)

3.通过基类的指针访问能访问到派生类的函数–基类的指针共享了派生类的方法(共享)

面试常考题

1.虚表存放在哪里

编译完成时,虚表应该已经存在;在使用的过程中,虚函数表不应该被修改掉(如果能修改,将会找不到对应的虚函数)——应该存在只读段——具体位置不同厂家有不同实现。

2.一个类中虚函数表有几张?

虚函数表(虚表)可以理解为是一个数组,存放的是一个个虚函数的地址

一个类可以没有虚函数表(没有虚函数就没有虚函数表);

可以有一张虚函数表(即使这个类有多个虚函数,将这些虚函数的地址都存在虚函数表中)即使在自己的类中定义一个虚函数,也是存到这个虚函数表中,这里不只是针对基类的;

也可以有多张虚函数表(继承多个有虚函数的基类)

3.虚函数的机制的底层实现是怎么样的?

虚函数机制的底层是通过虚函数表实现的,当类中定义了虚函数之后,就会在对象的存储位置开始,多存一份虚函数指针,指向虚函数的地址,然后使用指向派生类的基类的指针去访问虚函数的时候就会根据虚函数指针去虚函数表中找到虚函数(派生类的),然后去执行

4.三个概念的区分

重载 (overload) : 发生在同一作用域中, 当函数名称相同时 ,函数参数类型、顺序 、个数任一不同;

隐藏 (oversee) : 发生在基类派生类之间 ,函数名称相同时,就构成隐藏(参数不同也能构成隐藏);

覆盖(override): 发生在基类派生类之间,基类与派生类中同时定义返回类型、参数信息、名字都相同的虚函数,覆盖的是虚函数表中的入口地址,并不是覆盖函数本身

虚函数的限制

1.构造函数不能设置为虚函数,因为动态多态的激活条件是要创建对象,但是构造函数就是创造对象的,存在矛盾

2.静态成员函数不能设为虚函数,因为虚函数的实际调用是需要this指针来找虚函数指针,再找虚函数表,再找虚函数,但是静态成员函数是没有this指针的

3.inline函数不能设置为虚函数,因为inline函数实在编译期间完成替换的,而编译期间是无法实现动态多态的,起作用的时机是冲突的

4.普通函数不能设为虚函数,,虚函数是针对于对象多态的,跟普通函数没关系

虚函数的各种访问情况

虚函数机制的触发要求使用基类指针来调用虚函数

但是如果在构造函数和析构函数中访问虚函数呢?

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Grandpa
{
public:
Grandpa(){ cout << "Grandpa()" << endl; }
~Grandpa(){ cout << "~Grandpa()" << endl; }

virtual void func1() {
cout << "Grandpa::func1()" << endl;
}

virtual void func2(){
cout << "Grandpa::func2()" << endl;
}
};

class Parent
: public Grandpa
{
public:
Parent(){
cout << "Parent()" << endl;
//func1();//构造函数中调用虚函数
}

~Parent(){
cout << "~Parent()" << endl;
//func2();//析构函数中调用虚函数
}
};

class Son
: public Parent
{
public:
Son() { cout << "Son()" << endl; }
~Son() { cout << "~Son()" << endl; }

virtual void func1() override {
cout << "Son::func1()" << endl;
}

virtual void func2() override{
cout << "Son::func2()" << endl;
}
};

void test0(){
Son ss;
Grandpa * p = &ss;
p->func1();
p->func2();//调用是son的func1 和fcun2 说明发生了覆盖
}

Son隔了一层parent继承的func1和func2会不会发生覆盖?

会发生覆盖

所以如果多层继承,中间层没有发生覆盖,则在后面层在定义也是可以发生覆盖的

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Grandpa
{
public:
Grandpa(){ cout << "Grandpa()" << endl; }
~Grandpa(){ cout << "~Grandpa()" << endl; }

virtual void func1() {
cout << "Grandpa::func1()" << endl;
}

virtual void func2(){
cout << "Grandpa::func2()" << endl;
}
};

class Parent
: public Grandpa
{
public:
Parent(){
cout << "Parent()" << endl;
//func1();//构造函数中调用虚函数
}

~Parent(){
cout << "~Parent()" << endl;
//func2();//析构函数中调用虚函数
}
};

class Son
: public Parent
{
public:
Son()
//Grandpa() 调用grandpa构造函数会发生什么?会报错,因为granpa不是一个虚基类
:Parent()
{ cout << "Son()" << endl; }
~Son() { cout << "~Son()" << endl; }

virtual void func1() override {
cout << "Son::func1()" << endl;
}

virtual void func2() override{
cout << "Son::func2()" << endl;
}
};
void test0(){
Son ss;
Grandpa * p = &ss;
p->func1();
p->func2();
}

打印结果显而易见,Grandpa() Parent() Son() son::func1() son::func2 ~Son() ~Parent() ~Grandpa()

在构造函数和析构函数中调用虚函数

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Grandpa
{
public:
Grandpa(){ cout << "Grandpa()" << endl; }
~Grandpa(){ cout << "~Grandpa()" << endl; }

virtual void func1() {
cout << "Grandpa::func1()" << endl;
}

virtual void func2(){
cout << "Grandpa::func2()" << endl;
}
};

class Parent
: public Grandpa
{
public:
Parent(){
cout << "Parent()" << endl;
func1();//构造函数中调用虚函数
}

~Parent(){
cout << "~Parent()" << endl;
func2();//析构函数中调用虚函数
}
};

class Son
: public Parent
{
public:
Son()
//Grandpa() 调用grandpa构造函数会发生什么?会报错,因为granpa不是一个虚基类
:Parent()
{
cout << "Son()" << endl;
}
~Son()
{
cout << "~Son()" << endl;
}

virtual void func1() override {
cout << "Son::func1()" << endl;
}

virtual void func2() override{
cout << "Son::func2()" << endl;
}
};
void test0(){
Son ss;//会在调用parent构造函数的时候调用的是覆盖前的func函数还是覆盖后的呢?
}

会发现调用的是覆盖前的 就也就是说调用的granpa的func,
构造函数:因为parent走到func函数哪里的时候 grandpa的构造以及完成了,而son的构造函数几乎还没开始,所以这时候只能看到grandpa的func函数,所以会调用grandpa的func函数

析构函数:在Parent的析构函数执行时,此时Son的析构函数已经执行完了,可以理解为Son需要进行的回收工作都已经结束了,此时虚函数表也恢复为Parent的虚函数表。所以Parent的析构函数也只能看到本层及以上的部分

如果parent也对func1和func2进行了覆盖,那么parent调用的就是本层的func1和func2

在普通的成员函数中调用虚函数

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
39
40
41
42
43
44
45
46
47
48
49
class Base{
public:
Base(long x)
: _base(x)
{}

virtual void display() const{
cout << "Base::display()" << endl;
}

void func1(){
display();//通过this指针向上转型竟然可以调用到派生类的display
cout << _base << endl;
}

void func2(){
Base::display();
}
private:
long _base = 10;
};


class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)
, _derived(derived)
{}

void display() const override{
cout << "Derived::display()" << endl;
}
private:
long _derived;
};

void test0(){
Base base(10);
Derived derived(1,2);

base.func1();
base.func2();//这里会不会涉及虚函数机制,不会,因为调用的是基类本身的函数而且不是虚函数

derived.func1(); //调用了Derived::display();
derived.func2();//有作用域限定,所以调用的也是基类的
}

发现derived.func1()调用的是Derived::display();这个func1是通过基类子对象调用的(这也是基类指针指向派生类对象,this指针向上发生了转型,从derived转成了base )按道理来说应该调用的是Base的display(),因为是基类子对象的func1么,但是是**通过基类子对象调用的,this指针向上转型,变成了Base类型,这就符合动态多态的触发条件了

抽象类

抽象类的两种形式

1.定义了纯虚函数的类,称为抽象类

2.只定义了protected形构造函数的类,也成为抽象类

纯虚函数

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
class A
{
public:
virtual void print(){
cout << "A print()" << endl;
}
};

class B
:public A
{
public:
void print() override{
cout << "B print()" << endl;
}
};

class C
:public A
{
public:
void print() override{
cout << "C print()" << endl;
}
};

void test(){
B b1;
A *pa = &b1;
pa->print();

C c1;
pa = &c1;
pa->print();
}

不希望调用到基类的print可以把这个声明为纯虚函数

纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。纯虚函数的格式如下:

1
2
3
4
class 类名 {
public:
virtual 返回类型 函数名(参数 ...) = 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
25
26
27
28
29
30
31
32
33
34
35
36
class A
{
public:
virtual void print() = 0;//声明了纯虚函数
};

class B
:public A
{
public:
void print() override{
cout << "B print()" << endl;
}
};

class C
:public A
{
public:
void print() override{
cout << "C print()" << endl;
}
};

void test(){
A a1;//error A类已经是抽象类了,不能创建A类对象


B b1;
A *pa = &b1;
pa->print();

C c1;
pa = &c1;
pa->print();
}

如果B类C类没有实现完基类的所有纯虚函数呢

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
class A
{
public:
virtual void print() = 0;//声明了纯虚函数
virtual void show() = 0;
};

class B
:public A
{
public:
void print() override{
cout << "B print()" << endl;
}
void show() override{
cout << "B show()" << endl;
}
};

class C
:public A
{
public:
void print() override{
cout << "C print()" << endl;
}
};

void test(){
B b1;//success

C c1;//error 没有完全实现,不能创建对象
}

C类其实也没必要非要实现,其实可以交给C的派生类来实现

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
class C
:public A
{
public:
void print() override{
cout << "C print()" << endl;
}
};

class D
:public C
{
void show() override{
cout << " C show()" << endl;
}

};

void test(){
C c1;//error 还是不能创建,他也是抽象类,没有对所有的纯虚函数实现
D d1;
A *pa = &d1;
pa->print();
pa->show();
}

在A类中声明纯虚函数,A类就是抽象类,无法创建对象;

在B类中去覆盖A类的纯虚函数,如果把所有的纯虚函数都覆盖了(都实现了),B类可以创建对象;只要还有一个纯虚函数没有实现,B类也会是抽象类,也无法创建对象;

再往下派生C类,完成所有的纯虚函数的实现,C类才能够创建对象。

最顶层的基类(声明纯虚函数的类)虽然无法创建对象,但是可以定义此类型的指针,指向派生类对象,去调用实现好的纯虚函数。

—— 这种使用方式也归类为动态多态,尽管不符合第一个条件(基类中声明纯虚函数,而非定义),最终的效果仍然是基类指针调用到了派生类实现的虚函数,属于动态多态的特殊情况。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#define PI 3.14
class Figure{
public:
virtual string getName() const = 0;
virtual double getArea() const = 0;
};

void display(Figure & fig) {
cout << fig.getName()
<< "的面积是:"
<< fig.getArea() << endl ;
}

class Rectangle//矩形
: public Figure
{
public:
Rectangle(double len,double wid)
: _length(len)
, _width(wid)
{}

string getName() const override
{
return "矩形";
}
double getArea() const override
{
return _length * _width;
}
private:
double _length;
double _width;
};

class Circle
: public Figure
{
public:
Circle(double r)
: _radius(r)
{}

string getName() const override
{
return "圆形";
}
double getArea() const override
{
return PI * _radius * _radius;
}
private:
static const double PI = 3.14;//在这里初始化但是报错了,解决方式1:在类外初始化
//解决方式二
static constexpr double = 3.14;//对于int形式数据编译的时候就可以确定其值,而double需要在运行时才可以确定的,constexpr就是把运行时常量转换为编译时常量
double _radius;
};
const double Circle::PI = 3.14;

class Triangle
: public Figure
{
public:
Triangle(double a,double b,double c)
: _a(a)
, _b(b)
, _c(c)
{}

string getName() const override
{
return "三角形";
}
double getArea() const override
{
double p = (_a + _b + _c)/2;
return sqrt(p * (p -_a) * (p - _b)* (p - _c));
}
private:
double _a,_b,_c;
};
void test(){
Rectangle r1(1,2,3,4);
Circle c1(2);
Triangle t1(3,4,5);


display(r1);
display(c1);
display(t1);
//这里就是其强大之处 传参的时候就会让基类引用指向派生类引用了,从而实现多态
}

抽象类二:只定义了protected构造函数的类

如果一个类只定义了protected型的构造函数,而没有提供public构造函数,那么论是在外部还是在派生类中作为其对象成员都不能创建该类的对象,但可以由其派生出新的类,这种能派生新类,却不能创建自己对象的类是另一种形式的抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base{
protected:
Base(long base)
:_base(base)
{}
private:
long _base;
}

void test0(){
Base b1();//error
Base b1(10);//error
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base{
protected:
Base(long base)
:_base(base)
{}
private:
long _base;
}
class Derived
:public Base
{
Derived(long base,long derived)
:Base(base)
,_derived(derived)
{

}
private:
long _derived;
};
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
class Base{
public:
void print() const
{
cout << "_base:" << _base
<< ", _derived:" << _derived << endl;
}
protected:
Base(long base)
:_base(base)
{}
private:
long _base;
}
class Derived
:public Base
{
Derived(long base,long derived)
:Base(base)
,_derived(derived)
{

}
void print() const
{
cout << "_base:" << _base
<< ", _derived:" << _derived << endl;
}
private:
long _derived;
};

void test(){
Derived d1(4,5);
Base *b1 = &d1;
b1->print();//调用的是基类自己的,想要调用派生类的话,可以把print设为虚函数
}

这里Base不能直接创建对象,但是可以创建指针去指向派生类(或引用绑定)来调用派生类的方法

基类作为派生类的对象成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Derived
:public Base
{
Derived(long base,long derived)
:Base(base)//对于基类子对象可以调用构造函数,但对成员子对象不能调用构造函数,基类子对象可以确保保护权限的构造函数在派生类中调用
,_derived(derived)
,_b1(base)//error 也不允许,成员子对象的初始化不能使用Base的构造函数,这个就没法保证了,因为不止有派生类可以拥有成员子对象,非派生类也可以有
{

}
void print() const
{
cout << "_base:" << _base
<< ", _derived:" << _derived << endl;
}
private:
long _derived;
Base _b1;
};

O:单例类可以向下派生吗?

A:不可以,因为单例类的构造函数私有了,派生类肯定要调用基类的构造函数,但是创建不了,所有不可以。

析构函数设为虚函数

一般来说,如果类中定义了虚函数,析构函数也应该被定义为虚析构函数,尤其是类内有申请的动态内存,需要清理和释放的时候

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Base{
public:
Base()
:_base(new int(10))
{}
~Base(){
cout << "~Base()" << endl;
if(_base){
delete _base;
_base = nullptr;
}
}
virtual void display() const{
cout << "_base" << *_base << endl;
}

private:
int * _base;

}

class Derived
:public Base
{
public:
Derived()
:_derived(new int(20))//这里隐式的自动调用了Base的构造函数
{}
~Derived(){
cout << "~Derived()" << endl;
if(_derived){
delete _derived;
_derived= nullptr;
}
}
void display() const override{
cout << "_derived" << *_derived << endl;
}

private:
int * _derived;
};
void test(){
Base *pbase = new Derived();//基类指针指向派生类
pbase->display();//调用的肯定是派生类的display

//没有看到析构函数的打印信息,说明有内存泄漏,泄漏了32个字节


delete pbase();//发现还有四个字节空间被泄漏了,并且派生类的析构函数没有执行

}

如果析构函数不是虚函数的时候,pbase调用的是自己的析构函数,pbase是没法调用派生类的析构函数的,而用什么办法可以让pbase调用到派生类的方法呢,虚函数,只需要把Base的析构函数设置为虚函数,这样派生类的析构函数也可以对Base的析构函数实现覆盖

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Base{
public:
Base()
:_base(new int(10))
{}
virtual ~Base(){
cout << "~Base()" << endl;
if(_base){
delete _base;
_base = nullptr;
}
}
virtual void display() const{
cout << "_base" << *_base << endl;
}

private:
int * _base;

}

class Derived
:public Base
{
public:
Derived()
:_derived(new int(20))//这里隐式的自动调用了Base的构造函数
{}
~Derived(){
cout << "~Derived()" << endl;
if(_derived){
delete _derived;
_derived= nullptr;
}
}
void display() const override{
cout << "_derived" << *_derived << endl;
}

private:
int * _derived;
};
void test(){
Base *pbase = new Derived();//基类指针指向派生类
pbase->display();//调用的肯定是派生类的display

//没有看到析构函数的打印信息,说明有内存泄漏,泄漏了32个字节


delete pbase();//发现还有四个字节空间被泄漏了,并且派生类的析构函数没有执行

}

但是派生类凭啥能覆盖基类的析构函数呢?

语法规定

都已经覆盖了,为啥还能调用基类的析构函数呢?

派生类的析构函数执行完之后,一定会通过基类子对象去调用基类的析构函数的,没有走虚函数指针那条路

总结:

在实际的使用中,如果有通过基类指针回收派生类对象的需求,都要将基类的析构函数设为虚函数。

建议:一个类定义了虚函数,而且需要显示定义析构函数,就将它的析构函数设为虚函数。

验证虚表的存在

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
using namespace std;

class A{
public:
virtual void show() const{
cout <<"A show()" << endl;
}

virtual void display() const{
cout << "A display()" << endl;
}

virtual void print() const{
cout << "A print()" << endl;
}
};

class B
:public A
{
public:
void show() const override{
cout <<"B show()" << endl;
}

void display() const override{
cout << "B display()" << endl;
}

void print() const override{
cout << "B print()" << endl;
}

};

void test(){
B b;
long* pa = (long*)&b;

long* f = (long*)*pa;//取出来函数指针
typedef void(* Function)();
Function f1 =(Function) f[0];
f1();//B show()
f1 =(Function)f[1];
f1();//B display()
f1 =(Function) f[2];
f1();//B print()

}


int main()
{
test();
return 0;
}

这也说明了虚表是存在的