class X{};
class Y:virtual public X{};
class Z:virtual public X{};
class B:virtual public X{};
class A:public Y,public Z,public B{};
class M{};
class N:virtual public X,virtual public M
{
};
int main()
{
cout<<sizeof(X)<<endl;
cout<<sizeof(Y)<<endl;
cout<<sizeof(Z)<<endl;
cout<<sizeof(A)<<endl;
cout<<endl<<endl<<sizeof(N)<<endl;
return 0;
}
Code::Block 1 4 4 12 4
VS2008 1 4 4 12 8
C++ Standard并不强制规定如“base class subobjects的排列次序”,或“不同存取层级的data member的排列次序”。
C++对象模型尽量以空间优化和存取速度优化的考虑来表现nonstaticdata members,并且保持和C语言struct数据配置的兼容性。它把数据直接存放在每一个class object之中。对于继承而来的nonstatic data members也是如此。不过并没有强制定义其间的排列次序。
每一个class object因此必须有足够的大小以容纳它所有的nonstatic data members。因为它可能比你想象的还大。
1,由编译器自动加上的额外data members,用以支持某些语言特性(主要是各种virtual 特性)。
2,因为alignment(边界调整)的需要。
对member functions本身的分析,会直到整个class的声明都出现了才开始。因此,在一个inline member function躯体之内的一个datamember绑定操作,会在整个class声明完成之后才发生。
然而,这对于member function的argument list并不为真。Argemment list中的名称还是会在它们第一次遭遇时被适当地决议完成。
typedef intlength;
class Point
{
public:
void fun(length a)
{
cout<<typeid(a).name()<<endl; //i
cout<<typeid(_a).name()<<endl; //f
}
typedef float length;
length _a;
};
DataMemberr的布局
C++ Standard 要求,在同一个access section中,members的排列只需符合“较晚出现的members在class object中有较高的地址”这一条件即可,也就是说,各个members并不一定得连续排列,相对次序不会改变,什么东西可能会介于被声明的members之间呢?members的边界调整(alignment)可能 就需要填补一些bytes。编译器还可能会合成一些内部使用的data members,以支持整个对象模型。
C++ Standard秉持先前所说的“对于内存所持的放任态度”。
C++ Standard也允许编译器将多个access section之中的data members自由排列,不必在乎它们出现在class声明中的次序。
每一个static data member只有一个实体,存放在程序的data segment之中。
若取一个static data member的地址,会得到一个指向其数据类型的指针。
如果有两个classes,每一个都声明了一个static member freelist,那么当它们都被放在程序的data segment时,就会导致名称冲突。编译器的解决方法是暗中对每一个static data member编码,名字重组(name-mangleing),以获得一个独一无二的程序识别代码。
Nonstatic datamembers直接存放在每一个classobject之中。除非经由明确的或暗喻的classobject,没有办法直接存取它们。
欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移量(offset)。
无虚拟继承,每一个nonstatic data member的偏移量(offset)在编译时期即可获知,甚至如果member属于一个base class subobject(派生自单一或多重继承串链)也是一样。因此,存取一个nonstaticdata member,其效率和存取一个Cstruct member或一个nonderivedclass的member是一样的。
其继承结构中有一个virtual base class,并且被存取的member是一个从该virtualbase class继承而来的member时,就会有重大的差异。这个时候我们不能够说pt指向哪一种class type(因此我们也就不知道编译时期这个member真正的offset位置),所以这个存取操作必须延迟至执行期,以由一个额外的间接导引完成,才能够解决。
在C++继承模型中,一个derivedclass object所表现出来的东西,是其自己的members加上base classes members的总和,至于derived class members和base classes members的排列次序并未在C++ Standard中强制指定。理论上,编译器可以自由安排之。
class Point2d
{
protected:
float x,y;
}pt2d;
class Point3d
{
protected:
float z;
}pt3d;
C++保证“出现 在derived class中的base class subobject有其完整原样性”。
只加上单一继承
class Concrete1
{
private:
int val;
char bit1;
};
class Concrete2:public Concrete1
{
private:
char bit2;
};
class Concrete3:public Concrete2
{
private:
char bit3;
};
加上多态(Adding Polymorphism)
class Point2d
{
public:
Point2d(float x = 0.0,floaty = 0.0)
:_x(x),_y(y){}
virtualfloatz() const {return 0.0;}
virtualvoidz(float) {}
virtualconstPoint2d &operator +=(const Point2d& rhs)
{
cout<<"Point2d::+="<<endl;
_x += rhs._x;
_y += rhs._y;
return *this;
}
protected:
float _x,_y;
};
1,导入一个和Point2d有关的virtual table,用来存放它所声明的每一个virtual functions的地址。这个table的元素数目一般而言是被声明的virtual functions的数目,再加上一个或两个slots(用以支持runtime type identification)。
2,在每一个class object导入一个vptr,提供执行期的链接,使每一个object能够找到相应的virtual table
3,加强constructor,使它能够为vptr设定初值,让它能够指向class所对应的virtual table。这可能意味着在derived class和每一个base class的constructor中,重新设定vptr值。
4,加强destructor,使它能够抹消“指向class相关virtual table”的vptr。
class Point3d:public Point2d
{
public:
Point3d(float x = 0.0,floaty = 0.0,float z = 0.0)
:Point2d(x,y),_z(z){}
virtualfloatz() const {return _z;}
virtualvoidz(float newZ) {_z = newZ;}
virtual Point3d &operator +=(constPoint2d& rhs)
{
cout<<"Point3d::+="<<endl;
Point2d::operator+=(rhs);
_z += rhs.z();
return *this;
}
protected:
float _z;
};
多重继承(Multiple Inheritance)
多重继承的复杂度在于derived class和其上一个base class乃至上上一个base class之间的“非自然关系”。
class Point2d
{
public:
virtual~Point2d(){}
protected:
float_x,_y;
};
class Point3d:public Point2d
{
public:
virtual~Point3d(){}
protected:
float_z;
};
class Vertex
{
public:
virtual~Vertex(){}
protected:
Vertex *next;
};
class Vertex3d:public Point3d,publicVertex
{
public:
virtual~Vertex3d(){}
protected:
floatmumble;
};
对于一个多重派生对象,将其地址指定给“最左端(也就是第一个)baseclass的指针”,情况将和单一继承时相同,因为二者都指向指针相同的起始地址。至于第二个或后继的base class的地址指定操作,则需要将地址修改过:加上(或减去,如果downcase的话)介于中间的base class subobject(s) 的大小。
某些编译器设计有一种优化技术,只要第二个(或后继)base class声明了一个virtual function,而第一个base class没有,就把多个base classes的次序进行调换,这样可以在derived class object中少产生一个vptr。(Code::Block和VS2008能够做到)
虚拟继承(Virtual Inheritance)
一个不变局和一个共享局部。不变局部中的数据,不管后继如何衍化,总是拥有固定的offset,所以这一部分可以被直接存取,至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有所变化,所以它们只可以被间接存取。
在每一个derived class object中安插一些指针,每个指针指向一个virtual base class。要存取继承得来的virtual base members,可以使用相关指针间接完成。
每一个对象必须针对其每一个virtual base class背负一个额外的指针。然而理想上我们却希望class object有固定的负担,不因为其virtual base classes的数目而有所变化。
Microsoft编译器(VS2008就是这样做的)引入所谓所谓的virtual base classes。每一个class object如果有一个或多个virtual base classes,就会由编译器安插一个指针,指向virtual base class table。至于真正的virtual base class指针,当然是被放在该表格中。另一个方法(Code::Block这样做的),是在virtual function table中放置virtual base class 的offset。
一般而言,virtual base class最有效的一种运用形式就是:一个抽象的virtual base class,没有任何data member。
对象成员的效率(ObjectMember Efficiency)
封装是不会带来执行期的效率成本,使用inline存取函数亦然。
单一继承一般不会影响测试的效率,因为members被连续存储于derived class object中,并且其offset在编译时期就己知了。
虚拟继承的效率令人失望!编译器对虚基类数据的操作,产生间接存取操作,间接性压抑把所有的运算都移往缓存器执行的优化能力。但是间接性并不会严重影响非程序的执行效率。虚拟继承所带来的主要冲击是,它妨碍了优化的有效性。