《深度探索C++对象模型》第三章 Data语意学收获

1.虚继承

虚继承是建立在多重继承之上的,当多重继承时,有可能存在重复继承的情况,如下图所示

在a中,B1,B2虚继承了A,所以当D多重继承了B1和B2时,最后只是间接的继承了一个A类

在b中,因为没有使用虚继承,所以最后D间接继承了两个A类,显然这样是不行的。

虚继承类并不是存放在类对象空间中的,而是会在类对象空间中存放一个指针,来指向虚继承的存储空间。

 用此例来探究数据成员的存储结构

class X{};
class Y :public virtual X{};
class Z :public virtual X{};
class A :public Y, public Z{};


int main()
{
	cout << sizeof(X) << endl;
	cout << sizeof(Y) << endl;
	cout << sizeof(Z) << endl;
	cout << sizeof(A) << endl;
	system("pause");
}

继承关系如图所示: 

从这个例子中有以下几点可以总结:

①X是空类,也是会占一个字节的内存,这是编译器安插的一个char类型的字符,并且这一个字节是不会被继承的。

②Y和Z中都只有一个四字节的指针,这就是指向虚继承类的指针。(这只是针对VS,VC等编译器,有些编译器会是8字节)

③A是对Y和Z的自然继承,就是将这两个类中内容直接拷贝过来,而且自己又是空类,所以是8字节

class X{};
class Y :public X{};
class Z :public virtual X{};
class A :public Y, public Z{};


int main()
{
	cout << sizeof(X) << endl;
	cout << sizeof(Y) << endl;
	cout << sizeof(Z) << endl;
	cout << sizeof(A) << endl;
	system("pause");
}

如果去掉其中一个虚继承,占内存就是这样,但是这样在实际中使用肯定是有问题的。 

 

2.Data Member在对象存储空间中的布局(存储顺序)

只有非静态数据会存储在类对象的存储空间中,所以只需要考虑非静态数据在对象中存储空间的分布就行了。

一般编译器中都是按照声明声明顺序在存储空间中依次排列的,而vptr指针有的编译器放在对象开头,有的编译器放在对象结尾。

并且看一下下面这个类

class test
{
public:
    int a;
    double b;
private:
    char c;
    long d;
public:
    string e;
protected:
    short f;

}

这样的类在创建了一个类对象以后,对象空间中,还是依次按照a,b,c,d,e,f的顺序来存储非静态数据,并且这样写并不会造成额外的负担,MFC中这样的用法就很常见。

在这里就有一个奇怪的事,就是public,private,protected这些关键词去哪了,其实这些关键词都是编译器在起作用。以private为例,在编译阶段,编译器会检查private成员函数是否在类空间以外地方被使用,如果被使用了,编译是通不过的。

所以其实类对象从底层存储空间的角度来说,就是一个结构体,因为其他工作都是编译器来完成的。

3.模板实例化的两种方法

template <class T> T add(T a, T b)
{
	return a + b;
}


int main()
{
	cout << add<int>(3, 3) << endl;//方法一:最正规的方式使用模板实例化
	cout << add(1, 2) << endl;//方法二:这样也可以
	system("pause");
}

 模板并不一定需要提前声明,会在实例中使用第一个被声明的形式。但是方法一更好,因为在模板类中前后不一时,就会有影响

cout << add<int>(3, 3.1) << endl;//可以编译通过
cout << add(1, 2.1) << endl;//编译不通过

本来应该输入同一种类型的入口参数,但是现在输入一个int,一个double,这样方法一会把3.1强转成int类型的3,而方法二则认为模板不统一,会报错。

4.指向data member的指针

如果不定义变量,取类中非静态成员的地址,得到的是该非静态成员相对于该类首地址的偏移量,而静态数据成员则是实际地址(其实当这个类被定义并且静态数据成员被初始化以后,静态数据成员已经被创建并且分配内存了)

class point3d{
public:
	virtual void add();
	//static point3d origin;
	float x, y, z;
	static int a;
};

int point3d::a = 10;

int main()
{
	printf("%p\n", &point3d::x);
	printf("%p\n", &point3d::y);
	printf("%p\n", &point3d::z);
	printf("%p\n", &point3d::a);
	/*这样是错误的,得到的结果全是1
	cout << & point3d::x << endl;
	cout << &point3d::y << endl;
	cout << &point3d::z << endl;
	*/
	system("pause");
}


所以通过类对象使用非静态成员时,可以有如下等价

point3d origin;
origin.y=0.0;
//origin.y经过编译器的处理以后为:*(&origin+(&Point3d::y));

5.子类中data member在存储空间中的布局

除了子类自己的非静态成员变量会存储在对象空间中以外,还有继承的父类所有的的内容都会拷贝过来,包括字节对齐,如下

继承关系如下:

之所以要把父类包括对齐的字节都复制过来,是当父类指针指向子类对象时,父类指针可以使用的内存空间都是父类的,并不会出现子类独有的成员。

注意:vptr指针也是直接继承,如果父类有就直接继承,并且不会创建新的vptr,如果没有,但自类中virtual关键词,就会创建一个vptr。

6.多重继承的data member内存布局

以一个例子来阐述

现在有几个类的继承关系如图所示

其中内存分布如图所示

可以看到最后的子类Vertex3d中,先是复制的point3d的成员,再是复制的Vertex成员,最后是自己的成员。这个顺序也是按照继承的声明顺序来写的。

其中如果Vertex的指针指向这个对象,那么编译器会做出一系列处理来保证这个指针仍然指向Vertex3d对象中的Vertex那部分。

7.类中定义的成员函数都是默认内联的

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值