C++这门语言,几乎每个学校在大一的时候,都会去学习。但是其内在的对象布局,以及virtual
机制,我们又了解多少呢。为了仔细了解了解,前几天决定找点书来啃啃。然后就听闻网上的很多人推荐<深度探究C++对象模型>,就开始啃......但是可能是书比较老的缘故,很多说的很稳的道理,一实验就翻车了。所以,为了下次不翻车,就根据这本书中对象布局的讲述结构,以及查阅各种资料,在电脑实验,写下这篇博客。如果你想很清楚的弄明白C++多态,继承(单,多,虚),以及virtual
机制。那么就值得一看。
实验环境
:win10__Dev C++5.7.1__gcc 4.8.1__32-bit
C++对象模型
在谈对象布局之前,我们需要知道。在C++中,有俩种类成员数据:static
,nonstatic
。以及三种类成员函数:static
, nonstatic
, virtual
。那么在一个类,是如何错综复杂的摆放,上述的俩种数据成员,三种函数成员呢。其实说到底,就是分析下面五种模型。
- 无继承有虚函数
- 单继承无虚函数
- 单继承有虚函数(存在多态)
- 多重继承
- 虚拟继承(菱形继承)
0x01.无继承有虚函数
考虑如下代码。
class Point {
public:
Point(float val);
virtual ~Point();
float x() const;
static int PointCount();
private:
virtual ostream& print(ostream& os) const;
float _x;
static int _point_count;
};
成员摆放规则
- 非静态数据成员:保存在每一个对象里
- 静态数据成员:不在对象内
- 成员函数、静态函数:不在对象内
- 虚函数:每个类产生一个虚函数表(virtual table, vtbl),里面存放着一堆指向虚函数的指针。并且每个对象里,安插一个指针(vptr)指向该虚表。也就是说,多个对象共享一张虚表。
class Point {
public:
Point(){ }
virtual ~Point() { }
private:
};
/************************
* 在C++中,虚表并不一定是对象内排列的第一个,所以我们通过构造一个
* 没有数据成员,但是却存在虚函数的类,使虚表排列在第一个。(现今,
* 绝大多数编译器实现虚表时,都会把它放在最前面)至于成员分布,C++
* Standard要求:较晚出现的members,在对象中具有较高的地址。
************************/
int main(){
Point a, b;
cout << "a对象地址:" << &a << "\t" << "虚表地址:" << *(int*)&a << "\t" << endl;
cout << "b对象地址:" << &b << "\t" << "虚表地址:" << *(int*)&b << "\t" << endl;
return 0;
}
// 输出:
// a对象地址:0x29fe8c 虚表地址:4738776
// b对象地址:0x29fe88 虚表地址:4738776
0x02.单继承无虚函数
考虑如下代码。
class Point2d {
public:
Point2d():_x(0.0), _y(0.0) { }
// 其他函数
protected:
float _x;
float _y;
};
class Point3d:public Point2d {
public:
Point3d():Point2d(), _z(0.0){ }
// 其他函数
protected:
float _z;
};
这种模型相比较简单一些,就是把数据成员排列。至于排列顺序,C++Standard已经有了相对的要求。见0x01 代码注释。
0x03.单继承有虚函数(存在多态)
考虑如下代码。
class Point2d {
public:
Point2d():_x(0.0), _y(0.0) { }
virtual ~Point2d(){ }
virtual void func1() { cout << "Point2d::func1" << endl;}
virtual void reload() { cout << "Point2d::reload" << endl;}
// 其他函数
protected:
float _x;
float _y;
};
class Point3d:public Point2d {
public:
Point3d():Point2d(), _z(0.0){ }
virtual ~Point3d(){ }
virtual void func2() { cout << "Point3d::func2" << endl;}
void reload() { cout << "Point3d::reload" << endl;}
// 其他函数
protected:
float _z;
};
注意:无论在派生类中有没有使用virtual
函数,派生类中的虚表指针(vptr)都是指向另外一张表,与基类虚表(vtable)没有任何关系,不过除基类中的虚函数会被派生类的虚表所引用。但是如果出现虚函数被覆盖的情况,那么派生类虚表中的指针指向覆盖后的函数。如上图reload
与func1
的区别。通过派生类对虚函数的覆盖,然后向下转型,同一函数,不同体现,就是多态实现原理。测试代码如下:
/*******************************
* 虚表(vtable)地址: *(int *)&a
* 第一个虚函数地址:*(int *)*(int *)&a
* 第 i个虚函数地址:*(int *)*(int *)&a+i
* 但是在上图中,第一个却不是虚函数,当一个类的析够函数被定义为虚函
* 数,它会在虚表中占俩个格子。调用vtable[0],vtable[1]都是析够函数
* 。如果这个类是单个虚表的话,在vtable结束的地方,会补一个0.
*/
typedef void(*fun)(void);
int main(){
Point2d a;
Point3d c;
cout << "a对象虚表地址:" << *(int*)&a << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&a+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&a+i) << endl;
}
cout << endl << endl;
cout << "c对象虚表地址:" << *(int*)&c << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&c+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&c+i) << endl;
}
cout << endl;
((fun)*((int *)*(int *)&a+2))(); // 调用a.vtable[2]
((fun)*((int *)*(int *)&c+2))(); // 调用c.vtable[2]
((fun)*((int *)*(int *)&c+3))(); // 调用c.vtable[3]
return 0;
}
// 输出结果:
// a对象虚表地址:4738904
// -------------------------------
// 第0个虚函数地址: 4318248
// 第1个虚函数地址: 4318216
// 第2个虚函数地址: 4318032
// 第3个虚函数地址: 4318080
//
// c对象虚表地址:4738928
// -------------------------------
// 第0个虚函数地址: 4318500
// 第1个虚函数地址: 4318468
// 第2个虚函数地址: 4318032
// 第3个虚函数地址: 4318376
// 第4个虚函数地址: 4318328
//
// Point2d::func1
// Point2d::func1
// Point3d::reload
多重继承
考虑如下代码。
class Point2d {
public:
Point2d():_x(0.0), _y(0.0) { }
virtual ~Point2d(){ }
virtual void func1() { cout << "Point2d::func1" << endl;}
// 其他函数
protected:
float _x;
float _y;
};
class Vertex {
public:
Vertex(): next(NULL) { }
virtual ~Vertex() { }
virtual void func2() { cout << "Vertex::func2" << endl;}
//其他函数
protected:
Vertex* next;
};
class Vertex2d:public Point2d, public Vertex {
public:
Vertex2d():Point2d(), Vertex(){ }
virtual ~Vertex2d(){ }
virtual void func3() { cout << "Vertex2d::func3" << endl;}
// 其他函数
protected:
};
在多重继承的模型下,派生类内部布局会出现不止一张虚表,具体根据继承情况而定。而派生类的虚函数会被加到其中的一个虚表里,一般而言,是第一个继承父类想类似的虚表中。并且第一个虚表的也不会以0结束。在我电脑上是以-12。测试代码如下:
int main(){
Vertex2d a;
Point2d b;
Vertex c;
cout << "a对象虚表地址:"<< endl;
cout << "-------------------------------" << endl;
for(int i=0,j=1; i < 5; i++) {
if(*((int*)&a+i))
cout << "第"<< j++ <<"虚表地址:" << *((int*)&a+i) << endl;
}
// 查找虚表
// 如果不知道第一虚表以什么结束,尝试多输出几个
for(int i = 0; *((int *)*(int *)&a+i) != 0 && *((int *)*(int *)&a+i) != -12; i++){
cout <<"第1_" << i<<"个虚函数地址: "<< *((int *)*(int *)&a+i) << endl;
}
for(int i = 0; *((int *)*((int *)&a+3)+i) != 0 && *((int *)*(int *)&a+i) != -12; i++){
cout <<"第2_" << i<<"个虚函数地址: "<< *((int *)*((int *)&a+3)+i) << endl;
}
cout << endl << endl;
cout << "c对象虚表地址:" << *(int*)&c << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&c+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&c+i) << endl;
}
cout << endl << endl;
cout << "b对象虚表地址:" << *(int*)&b << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&b+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&b+i) << endl;
}
cout << endl << endl;
// ((fun)*((int *)*((int *)&a+3)+1))();
// ((fun)*((int *)*(int *)&a+1))();
return 0;
}
// a对象虚表地址:
// 第1虚表地址:4739048
// 第2虚表地址:4739072
// -------------------------------
// 第1_0个虚函数地址: 4319472
// 第1_1个虚函数地址: 4319440
// 第1_2个虚函数地址: 4319088
// 第1_3个虚函数地址: 4319336
// 第2_0个虚函数地址: 4670488
// 第2_1个虚函数地址: 4670480
// 第2_2个虚函数地址: 4318864
//
//
// c对象虚表地址:4739000
// -------------------------------
// 第0个虚函数地址: 4319008
// 第1个虚函数地址: 4318976
// 第2个虚函数地址: 4318864
//
//
// b对象虚表地址:4739024
// -------------------------------
// 第0个虚函数地址: 4319256
// 第1个虚函数地址: 4319224
// 第2个虚函数地址: 4319088
虚拟继承
回顾上面的多继承,假如Point2d
,Vertex
同时继承与另外一个基类,那么根据我们之前的分配规则,很容易可以得出,在派生类Vertex2d
里面会存在俩份基类的数据,这还是不是最可怕的,可怕的时候,当我们修改的时候,编译器完全不知道我们要修改那一个。从而编译错误。那么有没有办法解决呢,有,虚拟继承。考虑如下代码。
class Point2d {
public:
Point2d():_x(0.0), _y(0.0) { }
virtual ~Point2d(){ }
virtual void func1() { cout << "Point2d::func1" << endl;}
// 其他函数
protected:
float _x;
float _y;
};
class Vertex: virtual public Point2d {
public:
Vertex(): Point2d(), next(NULL) { }
virtual ~Vertex() { }
virtual void func2() { cout << "Vertex::func2" << endl;}
// 其他函数
protected:
Vertex* next;
};
class Point3d: virtual public Point2d {
public:
Point3d():Point2d(), _z(0.0){ }
virtual ~Point3d(){ }
virtual void func3() { cout << "Point3d::func3" << endl;}
// 其他函数
protected:
float _z;
};
class Vertex3d:public Point3d, public Vertex {
public:
Vertex3d():Point3d(), Vertex(){ }
virtual ~Vertex3d(){ }
virtual void func4() { cout << "Vertex3d::func4" << endl;}
// 其他函数
protected:
};
通过观察,对象分布图,我们可以看出,在最后的派生类中只出现了一次基类,达到我们期望的效果。测试代码如下:
int main(){
Vertex3d a;
Point3d b;
Vertex c;
Point2d d;
cout << "a对象虚表地址:" << endl;
for(int i=0,j=1; i < 7; i++) {
if(*((int*)&a+i))
cout << " 第"<< j++ <<"虚表地址:" << *((int*)&a+i) << endl;
}
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&a+i) != 0 && *((int *)*(int *)&a+i) != 8; i++){
cout <<"第1_" << i<<"个虚函数地址: "<< *((int *)*(int *)&a+i) << endl;
}
for(int i = 0; *((int *)*((int *)&a+2)+i) != 0 && *((int *)*(int *)&a+i) != 8; i++){
cout <<"第2_" << i<<"个虚函数地址: "<< *((int *)*((int *)&a+2)+i) << endl;
}
for(int i = 0; *((int *)*((int *)&a+2)+i) != 0 && *((int *)*(int *)&a+i) != 8; i++){
cout <<"第3_" << i<<"个虚函数地址: "<< *((int *)*((int *)&a+4)+i) << endl;
}
cout << endl << endl;
cout << "b对象虚表地址:" << *(int*)&b << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&b+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&b+i) << endl;
}
cout << endl << endl;
cout << "c对象虚表地址:" << *(int*)&c << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&c+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&c+i) << endl;
}
cout << endl << endl;
cout << "d对象虚表地址:" << *(int*)&d << endl;
cout << "-------------------------------" << endl;
for(int i = 0; *((int *)*(int *)&d+i) != 0; i++){
cout <<"第" << i<<"个虚函数地址: "<< *((int *)*(int *)&d+i) << endl;
}
cout << endl << endl;
return 0;
}
//输出结果
// a对象虚表地址:
// 第1虚表地址:4739404
// 第2虚表地址:4739432
// 第3虚表地址:4739460
// -------------------------------
// 第1_0个虚函数地址: 4320604
// 第1_1个虚函数地址: 4320572
// 第1_2个虚函数地址: 4320036
// 第1_3个虚函数地址: 4320416
// 第2_0个虚函数地址: 4671528
// 第2_1个虚函数地址: 4671520
// 第2_2个虚函数地址: 4319408
// 第3_0个虚函数地址: 4671724
// 第3_1个虚函数地址: 4671712
// 第3_2个虚函数地址: 4319788
//
//
// b对象虚表地址:4739340
// -------------------------------
// 第0个虚函数地址: 4320240
// 第1个虚函数地址: 4320208
// 第2个虚函数地址: 4320036
//
//
// c对象虚表地址:4739244
// -------------------------------
// 第0个虚函数地址: 4319612
// 第1个虚函数地址: 4319580
// 第2个虚函数地址: 4319408
//
//
// d对象虚表地址:4739304
// -------------------------------
// 第0个虚函数地址: 4319956
// 第1个虚函数地址: 4319924
// 第2个虚函数地址: 4319788
如有问题,欢迎提出,讨论。