C++对象模型系列1

在C++对象模型中,Nonstatic data members被配置于每一个class object之内,static data members则被放在个别的class object之外。static和nonstatic function members也被放在个别的class object之外。

virtual functions则通过下面两个步骤来实现:

  1. 每一个class产生出一堆指向virtual functions的指针,放在表格之中。这个表格被称为virtual table(vtbl)。
  2. 每一个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为vtpr。vtpr的设定和重置由每一个class的constructor、destructor和copy assignment运算符自动完成。每一个class所关联的type_info object(用来支持runtime type identification,RTTI)也经由virtual table被指出来,通常放在表格的第一个slot。

示例1:虚拟继承下的C++对象模型

在下面的代码示例中,Point3d和Vertex虚拟继承自Point2d,Vertex同时继承自Point3d和Vertex。

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
#include <iostream>

using namespace std;

class Point2d {
public:
virtual void print() {
cout << "I am Point2d" << endl;
}

virtual ~Point2d() {}

protected:
float _x; // 4字节
float _y;

};

class Vertex : public virtual Point2d {
public:
void print() {
cout << "I am Vertex" << endl;
}

~Vertex() {}

protected:
Vertex *next; // 指针,占8字节
};


class Point3d : public virtual Point2d {
public:
// virtual function print1是Point3d独有的虚函数,不是从基类那里继承来的,但是无论这个虚函数是否存在都不影响Point3d的对象模型
virtual void print1() {
cout << "This is my own virtual function, not inherient from my father" << endl;
}

~Point3d() {}

protected:
float _z;
};

class Vertex3d : public Vertex, public Point3d {
public:
~Vertex3d() {}

protected:
float mumble;
};

void test1() {
Vertex vx;
// vx.print();
cout << "sizeof(Point2d): " << sizeof(Point2d) << endl; // 16
cout << "sizeof(Point3d): " << sizeof(Point3d) << endl; // 32
cout << "sizeof(Vertex) : " << sizeof(Vertex) << endl; // 32
cout << "sizeof(Vertex3d) : " << sizeof(Vertex3d) << endl; // 48
}

int main() {
test1();
return 0;
}

写这个示例的目的是为了验证:在Point3d的对象模型中有两个虚表指针,一个是从其基类Point2d那里继承而来的,另一个是Point3d自身的。

Point2d的对象模型:

point2d
Point3d、Vertex、Vertex3d的对象模型。

point3d_etc

从上图可知,在Point2d中有两个float类型的成员变量和一个指针。故Point2d的大小为16字节;Point3d对象有一个Point2d子对象和一个float类型数据成员z、一个虚表指针Point3d,故大小为16+4+8 = 28,但是因为28不是8的倍数(在64位系统上运行的),所以28被边界对齐到32字节。同理Vertex的大小为16+8+8=32字节。对于Vertex3d,其大小为16+4+8+4+8+8 = 48字节。

示例2:继承模型下的地址

多重继承下的对象模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A {
public:
virtual ~A() {}
};

class B {
public:
virtual ~B() {}
};

class C : public A, public B {
};

void test1() {
C *pC = new C();
A *pA = (A *) pC;
B *pB = (B *) pC;
cout << pA << endl; // 0x5652962dbe70
cout << pB << endl; // 0x5652962dbe78
cout << pC << endl; // 0x5652962dbe70
cout << "sizeof(*pA):" << sizeof(*pA) << endl;
cout << "sizeof(*pB):" << sizeof(*pB) << endl;
cout << "sizeof(*pC):" << sizeof(*pC) << endl;
}

A、B和C的对象模型:
a_b

在将A类型的指针指向C类型的对象时,因为指针指向的实际上是C类型对象中的class A子对象。因为,在A和C类型对象中,A类型子对象都是位于相同的位置(位于对象开始处,偏移量为0),因此pA和pC的值相同。而在class C object中,B subobject的偏移量为8(即sizeof(A)),因此,pA和pC的值不同。