C++之多态

之前一直再看网络方面的内容,对C++部分的内容也没有再进行整理,多态是C++一个非常重要的特性,也是C++里面比较难理解的一个部分,这里主要分析其实现原理。


什么是多态?

维基百科:多态(英语:polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作。

简而言之,多态就是能够根据不同的对象而调用不同的接口。


多态分类

C++多态可以分为静态多态和动态多态。

在C++中,是通过虚函数来实现动态多态的。


虚函数

1
virtual <返回类型> 函数名(<参数列表>) {函数体;}
  • 如果在基类中定义了虚函数,派生类中的同名函数将自动变为虚函数
  • 如果派生类中重新定义了基类中的虚函数,这样程序就会根据对象类型而不是引用或指针的类型来选择方法

虚函数实现原理

虚函数表(vfptr):指向虚函数地址数组的指针。
内存调试可以直观的看到虚函数的实现,但是每次调试很麻烦,这里介绍一种打印虚函数表的方法:

单继承情况

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
#include <iostream>
using namespace std;
class Base {
public:
Base(int b = 1) : m_b(b) {};
~Base() {
cout << "~Base()" << endl;
}
virtual void Test1() {
cout << "Base:Test1()" << endl;
}
virtual void Test2() {
cout << "Base:Test2()" << endl;
}
virtual void Test3() {
cout << "Base:Test3()" << endl;
}
private:
int m_b;
};
class Derive : public Base{
public:
Derive(int d = 2) : m_d(d) {};
~Derive() {
cout << "~Derive()" << endl;
}
virtual void Test1() {
cout << "Derive:Test1()" << endl;
}
virtual void Test2() {
cout << "Derive:Test2()" << endl;
}
private:
int m_d;
};
typedef void(*VTable)(); // 函数指针
void PrintBase(Base &b) {
VTable vtb = (VTable)(*((int *)*(int *)&b));
int i = 0;
cout << "Base vftable: " << endl;
cout << "------------------------" << endl;
while (vtb != NULL) {
cout << "FUNCTION: " << ++i << endl;
vtb();
vtb = (VTable)*(((int*)(*(int *)&b)) + i);
}
cout << "------------------------" << endl;
}
void PrintDerive(Derive &d) {
VTable vtb = (VTable)(*((int *)*(int *)&d));
int i = 0;
cout << "Derive vftable: " << endl;
cout << "------------------------" << endl;
while (vtb != NULL) {
cout << "FUNCTION: " << ++i << endl;
vtb();
vtb = (VTable)*(((int*)(*(int *)&d)) + i);
}
cout << "------------------------" << endl;
}
int main() {
Base b;
PrintBase(b);
Derive d;
PrintDerive(d);
return 0;
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Base vftable:
------------------------
FUNCTION: 1
Base:Test1()
FUNCTION: 2
Base:Test2()
FUNCTION: 3
Base:Test3()
------------------------
Derive vftable:
------------------------
FUNCTION: 1
Derive:Test1()
FUNCTION: 2
Derive:Test2()
FUNCTION: 3
Base:Test3()
------------------------
~Derive()
~Base()
~Base()

可以看到,每一个类都维护了一个指针,这个指针指向一个函数指针数组,以NULL结尾,里面的地址就是虚函数的地址,从派生类中我们看出,如果派生类中重写了虚函数,对应的虚函数表也会替换成相应的函数。

多继承情况

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
#include <iostream>
using namespace std;
class Base1 {
public:
Base1(int b = 1) : m_b1(b) {};
~Base1() {
cout << "~Base1()" << endl;
}
virtual void Test1() {
cout << "Base1:Test1()" << endl;
}
virtual void Test2() {
cout << "Base1:Test2()" << endl;
}
virtual void Test3() {
cout << "Base1:Test3()" << endl;
}
private:
int m_b1;
};
class Base2 {
public:
Base2(int b = 2) : m_b2(b) {};
~Base2() {
cout << "~Base2()" << endl;
}
virtual void Test4() {
cout << "Base2:Test1()" << endl;
}
virtual void Test5() {
cout << "Base2:Test2()" << endl;
}
virtual void Test6() {
cout << "Base2:Test3()" << endl;
}
private:
int m_b2;
};
class Derive : public Base1, public Base2 {
public:
Derive(int d = 3) : m_d(d) {};
~Derive() {
cout << "~Derive()" << endl;
}
private:
int m_d;
};
int main() {
Derive d;
cout << sizeof(Derive) << endl;
return 0;
}

这里直接扔一张内存布局图。


纯虚函数

纯虚函数实在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”。
含有纯虚函数的类被称为抽象类,纯虚函数没有函数体,所以抽象类不允许实例化对象,抽象类的子类也可以是一个抽象类。抽象类子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。
如果在抽象类当中仅含有纯虚函数而不含其他任何东西,我们称之为接口类。

1
2
3
class Base {
virtual double fun() = 0;
};


参考资料:
C++多态篇1一静态联编,动态联编、虚函数与虚函数表vtable
C++多态篇2——虚函数表详解之从内存布局看函数重载,函数覆盖,函数隐藏
C++多态篇3——虚函数表详解之多继承、虚函数表的打印
c++ 深入理解虚函数