首先重新回顾一下关于类/对象大小的计算原则:
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
类的大小与数据成员有关与成员函数无关
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响
下面通过实例来展示虚继承和虚函数对类大小造成的影响。
测试环境为:Win32 + Vs2008
一、只出现虚继承的情况
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 |
#include <iostream> using namespace std; class BB class B1 : class B2 : class DD : int main ( B1 b1 ; cout<<&b1 <<endl; p = ( DD dd ; BB* pp ; pp = &dd ; |
从输出的地址和虚基类表成员数据可以画出对象内存模型图:
virtual base table
本类地址与虚基类表指针地址的差
虚基类地址与虚基类表指针地址的差
virtual base table pointer(vbptr)
从程序可以看出pp是BB* 指针,pp首先指向dd内存,当执行pp->bb_时,先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。
二、只出现虚函数的情况
C++ Code
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 Base
{
public :
virtual
void Fun1()
{
cout <<
"Base::Fun1 ..." << endl;
}
virtual
void Fun2()
{
cout <<
"Base::Fun2 ..." << endl;
}
int data1_ ;
};class Derived :
public Base
{
public :
void Fun2 ()
{
cout <<
"Derived::Fun2 ..." << endl;
}
virtual
void Fun3()
{
cout <<
"Derived::Fun3 ..." << endl;
}
int data2_ ;
};typedef
void (* FUNC)(
void );int main (
void)
{
cout <<
sizeof (Base) << endl;
cout <<
sizeof (Derived) << endl;
Base b ;
int **p = (
int **)& b;
FUNC fun = (FUNC) p[
0][
0];
fun();
fun = (FUNC )p[
0][
1];
fun();
cout << endl ;Derived d ;
p = (
int **)&d;
fun = (FUNC )p[
0][
0];
fun();
fun = (FUNC )p[
0][
1];
fun();
fun = (FUNC )p[
0][
2];
fun();
return
0;
}
从输出的函数体可以画出对象内存模型图:
vtbl:虚函数表(存放虚函数的函数指针)
vptr:虚函数表指针
从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。
三、虚继承与虚函数同时出现的情况:
C++ Code
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
#include <iostream>
using
namespace std;class BB
{
public :
virtual
void vfbb()
{
cout<<
"BB::vfbb" <<endl;
}
virtual
void vfbb2()
{
cout<<
"BB::vfbb2" <<endl;
}
int bb_ ;
};class B1 :
virtual
public BB
{
public :
virtual
void vfb1()
{
cout<<
"B1::vfb1" <<endl;
}
int b1_ ;
};class B2 :
virtual
public BB
{
public :
virtual
void vfb2()
{
cout<<
"B2::vfb2" <<endl;
}
int b2_ ;
};class DD :
public B1,
public B2
{
public :
virtual
void vfdd()
{
cout<<
"DD::vfdd" <<endl;
}
int dd_ ;
};typedef
void (* FUNC)(
void);int main (
void)
{
cout<<
sizeof (BB)<< endl;
cout<<
sizeof (B1)<< endl;
cout<<
sizeof (DD)<< endl;BB bb ;
int** p ;
p = (
int **)&bb;
FUNC fun ;
fun = (FUNC )p[
0][
0];
fun();
fun = (FUNC )p[
0][
1];
fun();
cout<<endl ;B1 b1 ;
p = (
int **)&b1;
fun = (FUNC )p[
0][
0];
fun();
fun = (FUNC )p[
3][
0];
fun();
fun = (FUNC )p[
3][
1];
fun();cout<<p [
1][
0]<<endl;
cout<<p [
1][
1]<<endl;
cout<<endl ;DD dd ;
p = (
int **)ⅆ
fun = (FUNC )p[
0][
0];
fun();
fun = (FUNC )p[
0][
1];
// DD::vfdd 挂在 B1::vfb1的下面
fun();
fun = (FUNC )p[
3][
0];
fun();
fun = (FUNC )p[
7][
0];
fun();
fun = (FUNC )p[
7][
1];
fun();
cout<<p [
1][
0]<<endl;
cout<<p [
1][
1]<<endl;
cout<<p [
4][
0]<<endl;
cout<<p [
4][
1]<<endl;
return
0;
}
从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:
注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有
虚基类表和虚基类表指针的存在。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
原文链接: https://www.cnblogs.com/jiangu66/p/3190354.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/95788
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!