您的位置:首页 > 编程语言 > C语言/C++

浅析GCC下C++多重继承 & 虚拟继承的对象内存布局

2012-07-18 12:48 316 查看
继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局。

一、多重继承

先看几个类的定义:

01
class
Top
02
{
03
public
:
04
int
a;
05
};
06
07
class
Left
:
public
Top
08
{
09
public
:
10
int
b;
11
};
12
13
class
Right
:
public
Top
14
{
15
public
:
16
int
c;
17
};
18
19
class
Bottom
:
public
Left,
public
Right
20
{
21
public
:
22
int
d;
23
};
不难想象,Left和Right类的内存布局如下图所示:



我们如下进行验证:

1
Left
*left=
new
Left();
2
Top
*top=left;
3
cout
<<left<<
'\t'
<<
top<<endl;
//输出:0x902c008
0x902c008
4
Right
*right=
new
Right();
5
top
=right;
6
cout
<<right<<
'\t'
<<
top<<endl;
//输出:0x902c018
0x902c018
从输出结果可以看出,父类指针top指向子类对象left和right的起始地址,与上述内存布局吻合。

在非虚拟多重继承的情况下,子类的内存布局是什么样子的呢?如下所示:



可以看出,Bottom类由于继承了Left和Right,而Left和Right又分别继承了Top。因此,Bottom包含了Top两次!

下面进行验证:

1
Bottom
*bottom=
new
Bottom();
1
//
top=bottom;//error:‘Top’isanambiguousbaseof‘Bottom’
1
top
=(Left*)bottom;
1
left
=bottom;
2
cout
<<bottom<<
'\t'
<<
top<<
'\t'
<<
left<<endl;
//输出:0x9930028
0x99300280x9930028
3
top
=(Right*)bottom;
4
right
=bottom;
5
cout
<<bottom<<
'\t'
<<
top<<
'\t'
<<
right<<endl;
//输出:0x9930028
0x99300300x9930030
从输出结果可以看出,left指针和right指针分别指向了bottom对象中它们所处的位置:





由于bottom对象中存在两部分top对象,因此不能直接用top指针指向bottom对象,因为编译器不知道你的意图到底是指向left中的bottom部分,还是right中的bottom部分。需要进行转换才可以。如果需要通过bottom指针分别访问left和right中的top部分,可以如下:bottom->Left::a,bottom->Right::a。

好了,到这里讲完了非虚拟继承下的多重继承的内存布局情况,相信大家应该有一个比较清晰的认识了。最重要的一点是:多重继承时,父类共同继承的祖父类会在子类中有多份存在。

二、虚拟继承

平时讨论的最多的是虚函数,很少涉及到虚拟继承的情况。那么,虚拟继承到底是一个什么概念呢?

先来看一个例子:

01
#include
<iostream>
02
using
namespace
std;
03
04
class
Father
05
{
06
public
:
07
int
a;
08
};
09
10
class
Child
:
virtual
public
Father
11
{
12
public
:
13
int
b;
14
};
15
16
int
main()
17
{
18
cout
<<
sizeof
(Father)
<<
'\t'
<<
sizeof
(Child)
<<endl;
//输出:4
12
19
Child
child;
20
cout
<<&child<<
'\t'
<<
&child.b<<
'\t'
<<
&child.a<<endl;
//输出:0xbfc08124
0xbfc081280xbfc0812c
21
return
0;
22
}
对,你没有看错,类的大小输出不是48,而是412。虚拟继承时,编译器会在子类中安插上一个虚表指针。

从输出的对象成员地址来看,我们可以得到Child类的如下内存布局:



现在我们对多重继承的例子进行改造:

01
class
Top
02
{
03
public
:
04
int
a;
05
};
06
07
class
Left
:
virtual
public
Top
08
{
09
public
:
10
int
b;
11
};
12
13
class
Right
:
virtual
public
Top
14
{
15
public
:
16
int
c;
17
};
18
19
class
Bottom
:
public
Left,
public
Right
20
{
21
public
:
22
int
d;
23
};
把Left和Right改成了虚拟继承Top。

从上面验证简单虚拟继承时,编译器安插虚表指针的例子,我们可以想象出此时Bottom类的对象内存布局如下:



对,你没有看错!虚拟继承时,子类只有父类共同继承的祖父类的一份存在。这其实也就是虚拟继承的最大用途。此时,Top,Left,Right和Bottom对象的大小分别为:4,12,12,24。

既然有虚表指针了,那么Bottom的虚表是什么样的呢?请看:



有了虚表,内存布局情况一目了然。下面我们进行验证:

1
Bottom
*bottom=
new
Bottom();
2
top
=bottom;
3
cout
<<bottom<<
'\t'
<<
top<<endl;
//输出:0x9fa5028
0x9fa503c
4
Left
*left=bottom;
5
cout
<<bottom<<
'\t'
<<
left<<endl;
//输出:0x9fa5028
0x9fa5028
6
Right
*right=bottom;
7
cout
<<bottom<<
'\t'
<<
right<<endl;
//输出:0x9fa5028
0x9fa5030
根据输出结果,我们可以知道指针的指向情况:



由于引入了虚指针和虚表,left指针和right指针可以根据虚表提供的偏移量信息,轻松访问到Top::a。

到此为止,已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况。总结下:非虚拟多重继承时,子类会有父类

共同继承祖父类的多份存在;虚拟继承时,子类会被安插一个虚拟指针;多重虚拟继承时,子类只有父类共同继承祖父类的一

份存在。通过父类的虚拟指针,可以正确地访问祖父类中的成员。

参考文献:

1.http://www.tbdata.org/archives/878

2.《深度探索C++对象模型》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: