无虚继承的空类占一个字节,用于标记该类。有虚继承的空类至少占4个字节(可能继承的空类占很大空间)。
对齐情况
class
X{
float i;//8
char j;//1
int k;//4
double
b;//下面重新的字节8,上面用来对齐
};
sizeof(X)=24
class Y{
char j;//1
int k;//4
};
sizeof(Y)=8
class Z{
char j;//1
int k;//4
double b;
};
sizeof(Y)=16
1.Data
Member的绑定
有两种情况:
情况一:在类的函数里面返回的变量,之前是全局变量,但如今已经是类的变量。例如:
extern float
x;
class
Point3d
{
Point3d(float,float,float);
//返回的是Point3d::x,早期时时全局变量x
float
X() const{return x;}
void X(float new_x) const{x=new_x;}
//...
float
x,y,z;
};
早期的防御措施:
把所有成员声明放在类的开头。
把所有的inline函数都放在class声明之外。
情况二:成员函数的参数列表中,当他们第一次出现的时候就适当的被决议resolved完成。
typedef int length;
class
Point3d
{
//所有的length被决议为global
//_val被决议为Point3d::_val
void
mumble(length val){_val=val;}
length mumble(){return
_val;}
//...
typedef float length;
float _val;
};
2.Data Member的布局
可以用下面模板函数测试布局的先后
template<typename
class_type,typename data_type1,typename data_type2>
char*
access_order(data_type1 class_type::*mem1,data_type2
class_type::*mem2)
{
assert(mem1!=mem2);
return mem1<mem2?"member 1 occurs
first":"member 2 occurs first";
}
class
X{
public:
double j;
int
k;
double
b;
};
access_order(&X::b,&X::j);//测试出错,额。。
3.成员变量的存取
对于例子:
Point3d origin,*pt=&origin;
//下面两种有区别么?根据class
Point3d和data member
x的角度来说,有区别。
origin.x=0.0
pt->x=0.0;
其实只有在当Point3d是一个derived
class,而在它的继承结构中有一个virtual base class,并且存取的member(如本例x)
是一个从该virtual base
class继承而来的member时,就会有很大区别。此时对于pt,只能在运行时候才决定,经有一个而外的间接导引。
对于origin,可以在编译时期就固定了。
情况一:x是静态成员变量
它被提出在class之外,并视为global变量。每个member的存取都不会导致任何空间或者执行时间的额外负担,无论是在个别的class
objects或者在static data
member本身。
上面两个都会转为:
Point3d::x=0.0;
即使对于函数返回值
foobar().x=24.0;
都会转为:
(void)foobar();
Point3d.x=24.0;
当对它取地址时,&Point3d::x;就会获得内存地址:const
int*
如果两个类都声明同样的static member,那么它们会被改名字
情况二:非静态成员变量
对其取地址:&origin::x;等于&origin+(&Point3d::x-1);
书上说是为了区别“一个指向data
member的指针,用以指向class的第一个member”和“一个指向data member的指针,没有指向class的第一个member”
4.继承与数据成员
只继承而不要多态时,派生类会根据父类的成员变量的最大长度来alignment。在继承链上,注意不能把数据放在用于alignment的padding区域中,因为
class
concrete1{
public:
int val;
char bit1;
}
class concrete2:public
concrete1{
char bit2;
}
如果把bit2放在bit1的padding域里面,如果concrete1
*p1;concrete2 *p2=*p1;时,bit2被乱覆盖掉了。
加上多态时,数据成员可能在虚指针的上面也可能在虚指针的下面。
多重继承,要适当地调整位置才可以赋值。如
class
Point2d{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
_x,_y;
};
class Point3d:public
Point2d{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
_x;
};
class
Vertex{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
Vertex
*next;
};
class Vertex3d:public Point3d,public
Vertex{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
mumble;
};
Vertex3d v3d;Vertex *pv;Point2d *p2d;Point3d
*p3d;
对于下面的赋值
pv=&v3d;//考虑到pv3d可能为0,加上偏移,需要转换为pv=pv3d?Vertex
*)(((char*)&v3d)+sizeof(Point3d)):0;
p2d=&v3d;p3d=&v3d;//直接拷贝地址就可以了。
虚拟继承有两种实现:1、每个派生类多加一个指针指向一个虚拟基类表2、在virtual中放置virtual base
class的offset,正值则索引到函数,负值则索引到基类。