《深度探索C++对象模型》读书笔记:第三章 Data语意学

3.1 Data Member的绑定

1.对于内联函数函数体内的data member绑定操作是在整个class声明完成之后才发生的,所以对于:

extern float x;
class Point3d
{
public:
	Point3d(float, float, float);
	float X() const { return x; }

private:
	float x, y, z;
};

函数X()中的x是Point3d的成员x,而不是那个extern的x。

2.但是对于成员函数的参数列表来说,其绑定操作是在第一次遇见他们是就发生了的,也就是在class没有被声明完之前,如果

typedef int length; 
class Point3d
{
public:
	void mumble(length val){ _val = val; }
	length mumble(){ return _val; }
private:
	typedef float length;
	length _val;
};

上述类中的成员函数中的length是int而不是float

为了避免这种情况,要把类中用到的类型别名的声明放在类的起始处

3.2 Data Member的布局

C++standard要求,在同一个access section(也就是private,public,protected等区段)中,members的排列只需符合“较晚出现的members在class object中有较高的地址”这一条件。不同access section没有规定。

3.3Data member的存取 

对于下面两种存取操作的区别

Point3d origin,*pt=&origin;
origin.x=0.0;
pt->x = 0.0;

static Data Member

上面两种存取操作对于static Data member 没有区别,不管该member是否是自身的或者是通过复杂的继承关系继承而来的。因为static data member位于class之外,并被视为一个global变量,存取static member不需要通过class object,通过'.'运算符进行存取,如上述的origin.x,只是语法上的一种便宜行事。

取一个static data member的地址,会得到一个指向其数据类型的指针,而不是一个指向其class member的指针,因为static member并不内含在一个class object之中

对于不同class中同名的static data member,编译器会对每一static member 编码(name-mangling),以获得独一无二的名称。

Nonstatic Data Members

nonstatic data member 直接存放在每一个class object之中,只有两种方式可以存取它们,一种是implicit,也就是通过this指针,一种是explict,也即是通过对象或指针。

对nonstatic data member 进行存取操作需要把class object 的起始地址加上data member的偏移量。对于member来说其偏移量在编译期即可获得,甚至是该member派生自单一或多重继承串链,都一样。因此存取一个nonstatic data member 其效率和存取一个C struct member或一个nonderived class 的member是一样的 。

但是对于虚拟继承来说就会出现不同,如下,如果x是从虚基类继承而来的,用对象进行存取和用指针进行存取

Point3d origin,*pt=&origin;
origin.x=0.0;
pt->x = 0.0;

由于original的类型是确定的为Point3d,所以x的偏移地址也即是确定的,但是对于pt,其真正指向的类型要在执行期才能确定,所以x的偏移地址(相对于pt指向的类型)也在执行期才能获得,要根据虚表指针所指向的虚表来确定。

3.4“继承”与Data member

只要继承不要多态

对于一个类

无继承时

class Point2d
{
public:
	//constructor(s)
	//operations
	//access functions
private:
	float x, y;
};

class Point3d
{
public:
	//constructor(s)
	//operations
	//access functions
private:
	float x, y,z;
};

内存布局与C struct完全一样

有继承无多态时

class Point2d
{
public:
	Point2d(float x = 0.0, float y = 0.0)
		:_x(x), _y(y){};
	float getx(){ return _x; }
	float gety(){ return _y; }
	void setx(float newX){ _x = newX; }
	void sety(float newY){ _y = newY; }
	void operator+=(Point2d& rhs)
	{
		_x += rhs.getx();
		_y += rhs.gety();
	}
	//.....more members
private:
	float _x;
	float _y;
};
class Point3d :public Point2d
{
public:
	Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
		:Point2d(x, y), _z(z){};
	float getz(){ return _z; }
	void setz(float newZ){ _z = newZ; }
	void operator+=(Point3d& rhs)
	{
		Point2d::operator+=(rhs);
		_z += rhs.getz();
	}
private:
	float _z;
};

布局为 

在设计继承时容易犯两个错误。

1.重复设计一些相同的操作

2.把class分解为两层或更多层,有可能会为了“表现class体系之抽象化”而膨胀所需空间。由于C++语言保证“出现在Derived class中的 base class subobject有其完整原样性”,所以导致了在成员变量存在内存对齐时,对class进行分层,可能会导致空间膨胀,举例如下

class Concrete
{
public:
	//...
private:
	int val;
	char c1;
	char c2;
	char c3;
};

 

现在假设将Concrete分为三层

class Concrete1
{
public:
	//...
private:
	int val;
	char bit1;
};
class Concrete2 :public Concrete1
{
public:
	//...
private:
	char bit2;
};
class Concrete3 :public Concrete2
{
public:
	//...
private:
	char bit3;
};

这就是Derived class中,base class subobject保持原样性。不会出现,在继承时,派生类占据基类部分的填补空间的情况。 

加上多态

1.会生成一个virtual table,用来存放所声明的每一个virtual function的地址。这个table的元素数目一般是所声明的虚函数的数目,在加上一个或两个slots(用以支持runtime type identification )

2.会导入一个vptr,提供执行期链接,使得每一个object能够找到相应的virtual table

3.加强constructor,使它能够为vptr设定初值,让它指向class所有的virtual table

4.加强destructor,使它能够抹消“指向class之相关virtual table”的vptr

多重继承

class Point2d
{
public:
	virtual void fun1();
protected:
	float _x, _y;
};

class Point3d :public Point2d
{
public:
	//...
protected:
	float _z;
};

class Vertex
{
public:
	virtual void fun2();
protected:
	Vertex *next;
};

class Vertex3d :public Point3d, public Vertex
{
public:
	//...
protected:
	float mulble;
};

如上面的类,继承关系为

 

在vs2013中布局为 

对于一个多重派生的对象,将其地址指定给“最左端(也就是第一个)base class的指针”,情况和单一继承时相同,因为二者指向相同的起始地址,需付出的成本只有地址指定操作而已。至于第二个或后继的base class的地址指定操作,则需要将地址修改过:加上或减去介于中间的base class subobject(s)大小。 

如果要存取第二个或后继base class中的一个data member,member的位置在编译时就固定了,因此存取member只是一个简单的offset运算,就像单一继承一样简单,不管是经由一个指针、一个reference或是一个object来存取

虚拟继承

编译器在实现虚拟继承时一般使用如下方法,class 如果内含一个或多个virtual base class subobject,如:

对于istream,将被分割为两部分:一个不变局部和一个共享局部,不变局部中的数据,不管后续如何衍化,总拥有固定的offset(从object的头算起),所以这一部分数据可以直接被存取。至于共享局部,所表现的就是virtual base class subobject.这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们只能被间接存取(编译器会安插指针,利用指针完成存取·)。

在vs2013中,是通过引入virtual baseclasstable,在对象的布局中会被安插一个指针指向该表,通过该表访问virtual base class

class Point2d
{
public:
protected:
	float _x, _y;
};

class Point3d :public virtual Point2d
{
public:
protected:
	float _z;
};

class Vertex :public virtual Point2d
{
public:
protected:
	Vertex *next;
};

class Vertex3d :public Vertex,public Point3d
{
public:
protected:
	float mulble;
};

对于上面这种继承关系,vs2013 中的布局为 

两个指针所指向的虚表如下,分别表示了virtual base class Point2d相对于Point3d和Vertex,在Vertex3d对象布局中地址的偏移

现在再来看之前的解释

对于虚拟继承来说就会出现不同,如下,如果x是从虚基类继承而来的,用对象进行存取和用指针进行存取

Point3d origin,*pt=&origin;
origin.x=0.0;
pt->x = 0.0;

由于original的类型是确定的为Point3d,所以x的偏移地址也即是确定的,但是对于pt,其真正指向的类型要在执行期才能确定,所以x的偏移地址(相对于pt指向的类型)也在执行期才能获得,要根据虚表指针所指向的虚表来确定。

 

                                                                                                       

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值