0%

C++ 虚函数表

参考文章:C++虚函数表剖析
参考文章:C++虚函数表分析
参考文章:C++中基类的析构函数为什么要用virtual虚析构函数


C++ 中的多态是用虚函数实现的:子类覆盖(重写)父类的虚函数,然后声明一个指向子类对象的父类指针,如 Base* base = new Derived(); 当调用base->func(),调用的是子类的Derived::func()

1
2
3
4
5
6
7
8
9
10
11
class Base
{
public:
virtual void func();
};

class Derived : public Base
{
public:
void func();
};

这种机制使用了一种动态绑定的技术,技术核心是虚函数表(简称虚表)。


虚函数表

每个包含了虚函数的类都有一个虚函数表(简称虚表)
当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可以调用这些虚函数,换句话说,就是一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。

1
2
3
4
5
6
7
8
9
class Base
{
public:
virtual void vFunc1();
virtual void vFunc2();

void Func1();
void Func2();
}

类Base包含虚函数 vFunc1()、vFunc2(),所以类Base拥有一张虚表。

类Base的虚表如下图:

哎呀,图片加载失败···

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。
普通函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。
虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就构造出来了。


虚表指针

虚表是属于类的,而不是属于某个具体的对象,同一个类的所有对象共享虚函数表;
为了指定对象的虚表,每个对象内部包含了一个虚表指针,来指向类的虚表;
为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针*__vptr,用来指向虚表,所以当类的对象被创建时便拥有了这个指针,且这个指针的值会被自动设置为指向类的虚表。


验证 (环境 Ubuntu 14.04 x86_64):

- [X] 对象共享虚表

  • 基础知识:
    32位的操作系统指针长度为4个字节,64位操作系统指针长度为8个字节;
    创建一个对象时,只为类中成员变量分配空间,对象之间共享成员函数。
  • _vptr:
    使用sizeof(Base)查看上述Base类的大小时,发现sizeof(Base)=8,说明编译器在类中自动添加了一个8字节的成员变量,这个变量就是__vptr,指向虚函数表的指针。

_vptr在对象内存中的位置应该和编译环境有关,本次测试使用gcc version 4.8.4

  • 测试代码(查看_vptr在对象内存中的位置):
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
test_vptr.hpp

class Base
{
public:
virtual void vFunc1()
{
PRINT_INFO("Base::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("Base::vFunc2()");
}

void Func1()
{
PRINT_INFO("Base::Func1()");
}

void Func2()
{
PRINT_INFO("Base::Func2()");
}

int m_data; //为了查看_vptr变量是放在对象内存的开始还是末尾
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test_vptr.cpp

/* Check _vptr is in the end/head of class instance! */
Base base;
char* p1 = reinterpret_cast<char* >(&base);
char* p2 = reinterpret_cast<char* >(&base.m_data);

if(p1 == p2)
{
PRINT_INFO("_vptr is in the end of class instance!");
}
else
{
PRINT_INFO("_vptr is in the head of class instance!");
}
PRINT_INFO("Instance base head point address: " << &base);
PRINT_INFO("Instance base m_data point address: " << &base.m_data);

输出结果:

[INFO]: _vptr is in the head of class instance!
[INFO]: Instance base head point address: 0x7fff18fe77e0
[INFO]: Instance base m_data point address: 0x7fff18fe77e8
  • 测试代码(对象共享虚表):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
test_vptr.cpp

Base base1;
char* p3 = reinterpret_cast<char* >(&base1);
//PRINT_INFO("instance base1 head point address: " << &base1);

Base* new_base = new Base;

//_vptr 类对象共享虚表,每个对象的虚表指针都指向相同的虚表
long* pBaseVptr = (long*)(&base); //_vptr指针的地址
long* baseVptr = (long*)(*pBaseVptr); //vptr 虚表的地址
long* pBase1Vptr = (long*)(&base1);
long* base1Vptr = (long*)(*pBase1Vptr);
long* pNewBaseVptr = (long*)new_base;
long* newBaseVptr = (long*)(*pNewBaseVptr);

//baseVptr == base1Vptr == newBaseVptr
PRINT_INFO("Instance base _vptr address: " << baseVptr);
PRINT_INFO("Instance base1 _vptr address: " << base1Vptr);
PRINT_INFO("Instance new_base _vptr address: " << newBaseVptr);

delete new_base;

输出结果:

[INFO]: Instance base _vptr address: 0x401d30
[INFO]: Instance base1 _vptr address: 0x401d30
[INFO]: Instance new_base _vptr address: 0x401d30

哎呀,图片加载失败···

- [X] 一般继承(无虚函数覆盖)

假如有如下的继承关系:

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
class Base
{
public:
virtual void vFunc1()
{
PRINT_INFO("Base::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("Base::vFunc2()");
}
};

//子类没有覆盖(重写)父类的虚函数
class Derived : public Base
{
public:
virtual void vFunc3()
{
PRINT_INFO("Derived::vFunc3()");
}

virtual void vFunc4()
{
PRINT_INFO("Derived::vFunc4()");
}
};

在子类的实例中,其虚函数表如下所示:

哎呀,图片加载失败···

从上边可以看出:

虚函数按照其声明顺序放于表中;
父类的虚函数在子类的虚函数前面。

  • 测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef void (*func)();

Derived d;
long* pvptr = (long*)(&d); //_vptr指针的地址
long* vptr = (long*)(*pvptr); //vptr 虚表的地址

//取虚表中的函数地址
func func1 = (func)vptr[0];
func func2 = (func)vptr[1];
func func3 = (func)vptr[2];
func func4 = (func)vptr[3];

//调用函数
func1();
func2();
func3();
func4();

输出结果:

[INFO]: Base::vFunc1()
[INFO]: Base::vFunc2()
[INFO]: Derived::vFunc3()
[INFO]: Derived::vFunc4()

- [X] 一般继承(有虚函数覆盖)

假如有如下继承关系:

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
class Base
{
public:
virtual void vFunc1()
{
PRINT_INFO("Base::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("Base::vFunc2()");
}
};

//子类覆盖(重写)父类的虚函数vFunc1
class Derived : public Base
{
public:
void vFunc1()
{
PRINT_INFO("Derived::vFunc1()");
}

//重定义(隐藏)
void vFunc2(int i)
{
PRINT_INFO("Derived::vFunc2()");
}
};

为了看到继承之后的效果,在上述继承关系中,只覆盖了父类的一个函数:vFunc1();
所以子类的实例,其虚函数表会是下边这个样子:

哎呀,图片加载失败···

从上边可以看出:

覆盖的函数vFunc1()被放到了虚表中原来父类虚函数vFunc1()的位置,没有被覆盖的函数依旧。

  • 测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef void (*func)();

Derived d;
long* pvptr = (long*)(&d); //_vptr指针的地址
long* vptr = (long*)(*pvptr); //vptr 虚表的地址

//取虚表中的函数地址
func func1 = (func)vptr[0];
func func2 = (func)vptr[1];
//func func3 = (func)vptr[2]; //子类的vFunc2函数不是重写,而且不是虚函数

//调用函数
func1();
func2();
//func3();

PRINT_INFO("--------------------");

Base* b = &d;
b->vFunc1();
b->vFunc2();

输出结果:

[INFO]: Derived::vFunc1()
[INFO]: Base::vFunc2()
[INFO]: --------------------
[INFO]: Derived::vFunc1()
[INFO]: Base::vFunc2()

由b所指的内存中的虚函数表的vFunc1()的位置已经被Derived::vFunc1()函数地址所取代,于是在实际调用发生时,是Derived::vFunc1()被调用了,这就实现了多态。

再看一种情况:

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

//C继承B, B继承A

class A
{
public:
A(int data1, int data2) : m_data1(data1), m_data2(data2)
{
}

virtual void vFunc1()
{
PRINT_INFO("A::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("A::vFunc2()");
}

void Func1()
{
PRINT_INFO("A::Func1()");
}

void Func2()
{
PRINT_INFO("A::Func2()");
}

private:
int m_data1;
int m_data2;
};

class B : public A
{
public:
B(int data1) : A(data1 + 1, data1 + 2), m_data3(data1)
{
}

void vFunc1()
{
PRINT_INFO("B::vFunc1()");
}

void Func1()
{
PRINT_INFO("B::Func1()");
}

private:
int m_data3;
};

class C : public B
{
public:
C(int data1, int data4) : B(data1 + 1), m_data1(data1), m_data4(data4)
{
}

void vFunc2()
{
PRINT_INFO("C::vFunc2()");
}

void Func2()
{
PRINT_INFO("C::Func2()");
}

private:
int m_data1;
int m_data4;

};

类A是基类,类B继承类A,类C又继承类B。类A,类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
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
A a(5,10);

long* pvptr_a = (long*)(&a); //类A _vptr指针的地址
long* vptr_a = (long*)(*pvptr_a); //vptr 虚表的地址

//取类A虚表中的函数地址
func a_vfun1 = (func)vptr_a[0];
func a_vfun2 = (func)vptr_a[1];

a_vfun1();
a_vfun2();

//取类A的对象a的成员对象
int a_m_data1 = (int)(*(pvptr_a + 1)); //跳过指针占用的8个字节
int a_m_data2 = (int)(*(((int*)(pvptr_a + 1)) + 1)); //int 占用4个字节
PRINT_INFO("Instance a A m_data1 = " << a_m_data1);
PRINT_INFO("Instance a A m_data2 = " << a_m_data2);

PRINT_INFO("-----------------------------------");

B b(15);

long* pvptr_b = (long*)(&b); //类B _vptr指针的地址
long* vptr_b = (long*)(*pvptr_b); //vptr 虚表的地址

//取类B虚表中的函数地址
func b_vfun1 = (func)vptr_b[0];
func b_vfun2 = (func)vptr_b[1];

b_vfun1();
b_vfun2();

//取类B的对象b的成员对象
int ba_m_data1 = (int)(*(pvptr_b + 1)); //跳过指针占用的8个字节
int ba_m_data2 = (int)(*(((int*)(pvptr_b + 1)) + 1)); //int 占用4个字节
int b_m_data3 = (int)(*(((int*)(pvptr_b + 1)) + 2));
PRINT_INFO("Instance b A m_data1 = " << ba_m_data1);
PRINT_INFO("Instance b A m_data2 = " << ba_m_data2);
PRINT_INFO("Instance b B m_data3 = " << b_m_data3);

PRINT_INFO("-----------------------------------");

C c(20, 30);

long* pvptr_c = (long*)(&c); //类C _vptr指针的地址
long* vptr_c = (long*)(*pvptr_c); //vptr 虚表的地址

//取类C虚表中的函数地址
func c_vfun1 = (func)vptr_c[0];
func c_vfun2 = (func)vptr_c[1];

c_vfun1();
c_vfun2();

//取类C的对象c的成员对象
int ca_m_data1 = (int)(*(pvptr_c + 1)); //跳过指针占用的8个字节
int ca_m_data2 = (int)(*(((int*)(pvptr_c + 1)) + 1)); //int 占用4个字节
int cb_m_data3 = (int)(*(((int*)(pvptr_c + 1)) + 2));
int c_m_data1 = (int)(*(((int*)(pvptr_c + 1)) + 3));
int c_m_data4 = (int)(*(((int*)(pvptr_c + 1)) + 4));
PRINT_INFO("Instance c A m_data1 = " << ca_m_data1);
PRINT_INFO("Instance c A m_data2 = " << ca_m_data2);
PRINT_INFO("Instance c B m_data3 = " << cb_m_data3);
PRINT_INFO("Instance c C m_data1 = " << c_m_data1);
PRINT_INFO("Instance c C m_data4 = " << c_m_data4);

PRINT_INFO("-----------------------------------");

A* pa_b = &b;
pa_b->vFunc1();
pa_b->vFunc2();
pa_b->Func1();
pa_b->Func2();

PRINT_INFO("-----------------------------------");

A* pa_c = &c;
pa_c->vFunc1();
pa_c->vFunc2();
pa_c->Func1();
pa_c->Func2();

PRINT_INFO("-----------------------------------");

B* pb_c = &c;
pb_c->vFunc1();
pb_c->vFunc2();
pb_c->Func1();
pb_c->Func2();

PRINT_INFO("-----------------------------------");

输出结果:

[INFO]: A::vFunc1()
[INFO]: A::vFunc2()
[INFO]: Instance a A m_data1 = 5
[INFO]: Instance a A m_data2 = 10
[INFO]: -----------------------------------
[INFO]: B::vFunc1()
[INFO]: A::vFunc2()
[INFO]: Instance b A m_data1 = 16
[INFO]: Instance b A m_data2 = 17
[INFO]: Instance b B m_data3 = 15
[INFO]: -----------------------------------
[INFO]: B::vFunc1()
[INFO]: C::vFunc2()
[INFO]: Instance c A m_data1 = 22
[INFO]: Instance c A m_data2 = 23
[INFO]: Instance c B m_data3 = 21
[INFO]: Instance c C m_data1 = 20
[INFO]: Instance c C m_data4 = 30
[INFO]: -----------------------------------
[INFO]: B::vFunc1()
[INFO]: A::vFunc2()
[INFO]: A::Func1()
[INFO]: A::Func2()
[INFO]: -----------------------------------
[INFO]: B::vFunc1()
[INFO]: C::vFunc2()
[INFO]: A::Func1()
[INFO]: A::Func2()
[INFO]: -----------------------------------
[INFO]: B::vFunc1()
[INFO]: C::vFunc2()
[INFO]: B::Func1()
[INFO]: A::Func2()
[INFO]: -----------------------------------

对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数。

- [X] 多重继承(无虚函数覆盖)

假如有如下继承关系:

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

virtual void vFunc2()
{
PRINT_INFO("A::vFunc2()");
}
};

class B
{
public:
virtual void vFunc1()
{
PRINT_INFO("B::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("B::vFunc2()");
}
};

class C
{
public:
virtual void vFunc1()
{
PRINT_INFO("C::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("C::vFunc2()");
}
};

//子类没有覆盖(重写)父类的虚函数
class Derived : public A, public B, public C
{
public:
virtual void vFunc3()
{
PRINT_INFO("Derived::vFunc3()");
}
};

对于子类实例中的虚函数表,如下所示:

哎呀,图片加载失败···

从上边可以看出:

每个父类都有自己的虚表;
子类的虚函数被放到了第一个父类的表中(所谓的第一个父类是按照声明顺序来判断的)。
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

PS: 上述“放到第一个父类的表中”描述不太准确,类Derived可以理解为有三张虚表(个人理解);
三张虚表中分别存在这A,B,C的虚函数地址,但是这三张表不是A,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
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
typedef void (*func)();

Derived d;

long* pvptr_a = (long*)(&d); //'类A' _vptr指针的地址
long* vptr_a = (long*)(*pvptr_a); //vptr 虚表的地址
//取'类A'虚表中的函数地址
func func1 = (func)vptr_a[0];
func func2 = (func)vptr_a[1];
func func3 = (func)vptr_a[2]; //子类的vFunc3函数

long* pvptr_b = (long*)((long*)(&d) + 1); //'类B' _vptr指针的地址
long* vptr_b = (long*)(*pvptr_b); //vptr 虚表的地址
//取'类B'虚表中的函数地址
func func4 = (func)vptr_b[0];
func func5 = (func)vptr_b[1];

long* pvptr_c = (long*)((long*)(&d) + 2); //'类C' _vptr指针的地址
long* vptr_c = (long*)(*pvptr_c); //vptr 虚表的地址
//取'类C'虚表中的函数地址
func func6 = (func)vptr_c[0];
func func7 = (func)vptr_c[1];

//调用函数
func1(); //A::vFunc1()
func2(); //A::vFunc2()
func3(); //Derived::vFunc3()
func4(); //B::vFunc1()
func5(); //B::vFunc2()
func6(); //C::vFunc1()
func7(); //C::vFunc2()

PRINT_INFO("--------------------");

A a;
B b;
C c;

long* pvptr_aa = (long*)(&a); //类A _vptr指针的地址
long* vptr_aa = (long*)(*pvptr_aa); //vptr 虚表的地址

//取类A虚表中的函数地址
func func8 = (func)vptr_aa[0];
func func9 = (func)vptr_aa[1];

long* pvptr_bb = (long*)(&b); //类B _vptr指针的地址
long* vptr_bb = (long*)(*pvptr_bb); //vptr 虚表的地址

//取类B虚表中的函数地址
func func10 = (func)vptr_bb[0];
func func11 = (func)vptr_bb[1];

long* pvptr_cc = (long*)(&c); //类C _vptr指针的地址
long* vptr_cc = (long*)(*pvptr_cc); //vptr 虚表的地址

//取类C虚表中的函数地址
func func12 = (func)vptr_cc[0];
func func13 = (func)vptr_cc[1];

func8(); //A::vFunc1()
func9(); //A::vFunc2()
func10(); //B::vFunc1()
func11(); //B::vFunc2()
func12(); //C::vFunc1()
func13(); //C::vFunc2()

PRINT_INFO("--------------------");

//class A,B,C vptr
PRINT_INFO("Class A vptr address: " << vptr_aa);
PRINT_INFO("Class B vptr address: " << vptr_bb);
PRINT_INFO("Class C vptr address: " << vptr_cc);

//class Derived vptr
PRINT_INFO("Class Derived 'A' vptr address: " << vptr_a);
PRINT_INFO("Class Derived 'B' vptr address: " << vptr_b);
PRINT_INFO("Class Derived 'C' vptr address: " << vptr_c);

//class Derived vfunc address
PRINT_INFO("Class Derived A::vFunc1() address: " << (long*)func1);
PRINT_INFO("Class Derived A::vFunc2() address: " << (long*)func2);
PRINT_INFO("Class Derived Derived::vFunc3() address: " << (long*)func3);
PRINT_INFO("Class Derived B::vFunc1() address: " << (long*)func4);
PRINT_INFO("Class Derived B::vFunc2() address: " << (long*)func5);
PRINT_INFO("Class Derived C::vFunc1() address: " << (long*)func6);
PRINT_INFO("Class Derived C::vFunc2() address: " << (long*)func7);
PRINT_INFO("Class A A::vFunc1() address: " << (long*)func8);
PRINT_INFO("Class A A::vFunc2() address: " << (long*)func9);
PRINT_INFO("Class B B::vFunc1() address: " << (long*)func10);
PRINT_INFO("Class B B::vFunc2() address: " << (long*)func11);
PRINT_INFO("Class C C::vFunc1() address: " << (long*)func12);
PRINT_INFO("Class C C::vFunc2() address: " << (long*)func13);

//没有多态,继承之后不重写虚函数,没啥意义
Derived1 d1;
A* pa = &d1;
pa->vFunc1();
pa->vFunc2();
//pa->vFunc3();//error: ‘class A’ has no member named ‘vFunc3’

B* pb = &d1;
pb->vFunc1();
pb->vFunc2();
//pb->vFunc3();//error: ‘class B’ has no member named ‘vFunc3’

C* pc = &d1;
pc->vFunc1();
pc->vFunc2();
//pc->vFunc3();//error: ‘class C’ has no member named ‘vFunc3’

输出结果:

[INFO]: A::vFunc1()
[INFO]: A::vFunc2()
[INFO]: Derived::vFunc3()
[INFO]: B::vFunc1()
[INFO]: B::vFunc2()
[INFO]: C::vFunc1()
[INFO]: C::vFunc2()
[INFO]: --------------------
[INFO]: A::vFunc1()
[INFO]: A::vFunc2()
[INFO]: B::vFunc1()
[INFO]: B::vFunc2()
[INFO]: C::vFunc1()
[INFO]: C::vFunc2()
[INFO]: --------------------
[INFO]: Class A vptr address: 0x602d30
[INFO]: Class B vptr address: 0x602d10
[INFO]: Class C vptr address: 0x602cf0
[INFO]: Class Derived 'A' vptr address: 0x602c70
[INFO]: Class Derived 'B' vptr address: 0x602c98
[INFO]: Class Derived 'C' vptr address: 0x602cb8
[INFO]: Class Derived A::vFunc1() address: 0x401678
[INFO]: Class Derived A::vFunc2() address: 0x4016be
[INFO]: Class Derived Derived::vFunc3() address: 0x40181c
[INFO]: Class Derived B::vFunc1() address: 0x401704
[INFO]: Class Derived B::vFunc2() address: 0x40174a
[INFO]: Class Derived C::vFunc1() address: 0x401790
[INFO]: Class Derived C::vFunc2() address: 0x4017d6
[INFO]: Class A A::vFunc1() address: 0x401678
[INFO]: Class A A::vFunc2() address: 0x4016be
[INFO]: Class B B::vFunc1() address: 0x401704
[INFO]: Class B B::vFunc2() address: 0x40174a
[INFO]: Class C C::vFunc1() address: 0x401790
[INFO]: Class C C::vFunc2() address: 0x4017d6
[INFO]: A::vFunc1()
[INFO]: A::vFunc2()
[INFO]: B::vFunc1()
[INFO]: B::vFunc2()
[INFO]: C::vFunc1()
[INFO]: C::vFunc2()

输出结果可以看出:
虚函数共享,虚表不共享

- [X] 多重继承(有虚函数覆盖)

假如有如下继承关系:

1
2
3
4
5
6
7
8
9
10
11
//父类 A B C 同上

//子类覆盖(重写)父类的VFunc1()虚函数
class Derived : public A, public B, public C
{
public:
void vFunc1()
{
PRINT_INFO("Derived::vFunc1()");
}
};

对于子类实例中的虚函数表,如下所示:

哎呀,图片加载失败···

从上边可以看出:

三个父类虚函数表中的vFunc1()的位置被替换成了子类的函数指针,这样就实现了多态。

  • 测试代码:
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
Derived d;

long* pvptr_a = (long*)(&d); //'类A' _vptr指针的地址
long* vptr_a = (long*)(*pvptr_a); //vptr 虚表的地址
//取'类A'虚表中的函数地址
func func1 = (func)vptr_a[0];
func func2 = (func)vptr_a[1];
func func3 = (func)vptr_a[2]; //子类的vFunc3函数

long* pvptr_b = (long*)((long*)(&d) + 1); //类B _vptr指针的地址
long* vptr_b = (long*)(*pvptr_b); //vptr 虚表的地址
//取'类B'虚表中的函数地址
func func4 = (func)vptr_b[0];
func func5 = (func)vptr_b[1];

long* pvptr_c = (long*)((long*)(&d) + 2); //'类C' _vptr指针的地址
long* vptr_c = (long*)(*pvptr_c); //vptr 虚表的地址
//取'类C'虚表中的函数地址
func func6 = (func)vptr_c[0];
func func7 = (func)vptr_c[1];

//调用函数
func1(); //Derived::vFunc1()
func2(); //A::vFunc2()
func3(); //Derived::vFunc3()
func4(); //Derived::vFunc1()
func5(); //B::vFunc2()
func6(); //Derived::vFunc1()
func7(); //C::vFunc2()

PRINT_INFO("---------------------------");

A* pa = &d;
pa->vFunc1();
pa->vFunc2();

B* pb = &d;
pb->vFunc1();
pb->vFunc2();

C* pc = &d;
pc->vFunc1();
pc->vFunc2();

//父类指针调用子类中未覆盖父类的虚成员函数,编译器视为非法
//pa->vFunc3(); //error: ‘class A’ has no member named ‘vFunc3’ pa->vFunc3();
//pb->vFunc3(); //error: ‘class B’ has no member named ‘vFunc3’ pb->vFunc3();
//pc->vFunc3(); //error: ‘class C’ has no member named ‘vFunc3’ pc->vFunc3();

//可通过另一种方法实现,通过指针的方式访问虚函数表来达到违反C++语义的行为
func pa_vFunc3 = (func)(((long*)(*(long*)pa))[2]);

pa_vFunc3();

输出结果:

[INFO]: Derived::vFunc1()
[INFO]: A::vFunc2()
[INFO]: Derived::vFunc3()
[INFO]: Derived::vFunc1()
[INFO]: B::vFunc2()
[INFO]: Derived::vFunc1()
[INFO]: C::vFunc2()
[INFO]: ---------------------------
[INFO]: Derived::vFunc1()
[INFO]: A::vFunc2()
[INFO]: Derived::vFunc1()
[INFO]: B::vFunc2()
[INFO]: Derived::vFunc1()
[INFO]: C::vFunc2()
[INFO]: Derived::vFunc3()

虚析构函数

C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用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
43
44
45
46
47
48
49
50
51
52
53
class Base
{
public:
Base()
{
PRINT_INFO("Base::Base()");
}

virtual ~Base()
{
PRINT_INFO("Base::~Base()");
}

virtual void vFunc1()
{
PRINT_INFO("Base::vFunc1()");
}

virtual void vFunc2()
{
PRINT_INFO("Base::vFunc2()");
}

};

class Derived : public Base
{
public:
Derived()
{
PRINT_INFO("Derived::Derived()");
}

~Derived()
{
PRINT_INFO("Derived::~Derived()");
}
};

//验证虚析构函数在虚表中
typedef void (*func)();

Derived d;

func func1 = (func)(((long*)(*((long*)(&d))))[0]); //~Derived()
func func2 = (func)(((long*)(*((long*)(&d))))[1]); //这个指针不知是指向谁的
func func3 = (func)(((long*)(*((long*)(&d))))[2]); //Base::vFunc1()
func func4 = (func)(((long*)(*((long*)(&d))))[3]); //Base::vFunc2()
func1();
//func2();
func3();
func4();

输出结果:

[INFO]: Base::Base()
[INFO]: Derived::Derived()
[INFO]: Derived::~Derived()
[INFO]: Base::~Base()
[INFO]: Base::vFunc1()
[INFO]: Base::vFunc2()
[INFO]: Derived::~Derived()
[INFO]: Base::~Base()

输出结果显示,析构函数被调用了两次,第一次是通过func1()调用的,后边是正常释放对象d。
上述func1()函数调用的打印说明,在调用Derived析构函数的时候也执行了Base的析构函数,所以在Derived析构函数中调用了Base的析构函数。

1
2
3
4
5
6
PRINT_INFO("---------------------");

Base* pb = new Derived;
delete pb;

PRINT_INFO("---------------------");

输出结果:

[INFO]: ---------------------
[INFO]: Base::Base()
[INFO]: Derived::Derived()
[INFO]: Derived::~Derived()
[INFO]: Base::~Base()
[INFO]: ---------------------

如果Base的析构函数不是virtual的,则输出结果是只会打印Base::~Base()。

基类指针指向了派生类对象,而基类中的析构函数如果是非virtual的,之前讲过,虚函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete时候只会调用基类的析构函数,而不会调用派生类的析构函数。

经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。


纯虚函数

包含纯虚函数的类称之为抽象类。
对于抽象类来说,它无法实例化对象,而对于抽象类的子类来说,只有把抽象类中的纯虚函数全部实现之后,子类才可以实例化对象。


安全性

C++的不安全性,用虚函数表来违反C++语义的行为

通过父类型的指针,访问子类自己的虚函数

子类没有覆盖父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重写的。
虽然从上面我们可以看到A的虚表中有Derived的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

1
2
3
4
Derived d;
A* pa = &d;
pa->vFunc3(); //error: ‘class A’ has no member named ‘vFunc3’ pa->vFunc3();

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。

1
2
3
4
typedef void (*func)();

func pa_vFunc3 = (func)(((long*)(*(long*)pa))[2]);
pa_vFunc3();

访问non-public的虚函数

如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数。

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 Base
{
private:
virtual void vFunc1()
{
PRINT_INFO("private Base::vFunc1()");
}

protected:
virtual void vFunc2()
{
PRINT_INFO("protected Base::vFunc2()");
}
};

class Derived : public Base
{

};
//如果Base中虚函数限定符是pubilc的,Derived通过private或者是protected继承Base,效果一样

//通过指针访问private protected保护的虚函数
Derived d;
//d.vFunc1(); //error: ‘virtual void Base::vFunc1()’ is private
//d.vFunc2(); //error: ‘virtual void Base::vFunc2()’ is protected

long* pvptr = (long*)(&d);
long* vptr = (long*)(*pvptr);

func func1 = (func)vptr[0];
func func2 = (func)vptr[1];

func1();
func2();

输出结果:

[INFO]: private Base::vFunc1()
[INFO]: protected Base::vFunc2()