Data语意学
Posted along4396
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Data语意学相关的知识,希望对你有一定的参考价值。
一、空类大小
#include<iostream> class X { }; class Y:public virtual X { }; class Z:public virtual X { }; class A:public Y,public Z { }; class Point2d { float x; float y; }; class Point3d:public Point2d { }; int main() { std::cout<<"sizeof(X): "<<sizeof(X)<<" " <<"sizeof(Y): "<<sizeof(Y)<<" " <<"sizeof(Z): "<<sizeof(Z)<<" " <<"sizeof(A): "<<sizeof(A)<<" "; return 0; }
以上的代码,在linux上编译的输出结果为:
sizeof(X): 1 sizeof(Y): 8 sizeof(Z): 8 sizeof(A): 16
对于以上的输出,深度探索C++对象模型的解释是:
1. c++空类对象的大小不为0,它有一个隐藏的1byte大小,用于让同一个class的两个objects在内存中有独一无二的地址
2.语言本身所造成的额外负担,若有virtual base class,那么在derived class中,将额外存在一个指针大小的空间。该指针或者指向virtual base class subobject,或者指向一个相关的表格(表格存放或者是subobject的地址,或者是subobject的偏移位置)
3.编译器对于特殊情况所提供的优化处理
4.alignment的限制
class Y对象的内存分布为:
class Z对象的内存分布为:
不同的编译器对于empty virtual base classes的设计可能不同,在某些编译器中,一个empty virtual base class 被视为是derived class 的最开头的一部分,也即是说它没有花费任何额外空间,这就节省了以上提到的1byte空间爱in,也不再需要3byte的填充。
二、继承和Data Member
1. 只要继承不要多态
一般而言,具体继承(concrete inheritance)相对于虚拟继承并不会增加空间或者存取时间上的负担。
class Point2d { public: Point2d(float x=0,float y=0) :m_x(x),m_y(y) { } float x() const { return m_x; } float y() const { return m_y; } void operator+=(const Point2d& rhs) { m_x += rhs.x(); m_y += rhs.y(); } private: float m_x; float m_y; }; class Point3d:public Point2d { public: Point3d(float x =0,float y=0,float z =0) :Point2d(x,y),m_z(z) { } float z() const { return m_z; } void operator+=(const Point3d& rhs) { Point2d::operator+=(rhs); m_z += rhs.z(); } private: float m_z; };
以上对Point2d和Point3d的设计中,Point2d和Point3d对象的内存分布为
但两个原本独立不相关的classes被凑成一个"type/subtype",并且带有继承关系,常常容易犯下以下错误:
1. 重复设计一些相同操作的函数,例如以上的constructor和operator+=,他们并没有设计为inline函数,Point3d的初始化操作和加法操作,将需要部分的Point2d object和部分Point3d object作为成本
2. 把一个class分解成两层或者更多层,有可能会为了“表现class体系的抽象化”而膨胀所需的空间,例如以下的例子
class Concrete { private: int val1; char c1; char c2; char c3; }; class Concrete1:public Concrete { private: char c4; }; class Concrete2:public Concrete1 { private: char c5; }; class Concrete3:public Concrete2 { private: char c6; };
在这样的继承体系下,每层由于继承而导致的额外的字节填充是很大的。
2. 加上多态
如果座标点的加法,只能处理Point2d加Point2d,Point3d加Point3d的形式,往往不是我们要的结果。通常想要的座标加法是不管座标的类型都能正确处理的接口,我们可以在继承关系中提供一个virtual function接口。
例如以下:
class Point2d { public: Point2d(float x=0,float y=0) :m_x(x),m_y(0) { } float x() const { return m_x; } float y() const { return m_y; } // 设定z(),operator+=()为虚函数 virtual float z() const { return 0.0; // 2d点的z为0.0 } virtual void operator+=(const Point2d& rhs) { m_x = rhs.x(); m_y = rhs.y(); } private: float m_x; float m_y; }; class Point3d:public Point2d { public: Point3d(float x=0,float y=0,float z=0) :Point2d(x,y),m_z(z) { } float z() const { return m_z; } void operator+=(const Point2d& rhs) // 注意参数类型为const Point2d&而不是const Point3d& { Point2d::operator+=(rhs); m_z = rhs.z(); } private: float m_z; };
以上的做法使得Point2d有了额外的空间和存取时间负担。
1.导入和Point2d有关的virtual table,用来存放它所声明的每一个virtual function的地址。
2.在每个class object中导入vptr,提供执行期的链接,使得每个object能找到相应的virtual table.
3.改变constructor,使得它能够设定vptr的值,让它指向class对应的virtual class.
4.改变destructor,使得它能抹消“指向class相关的virtual table”的vptr。
以上声明的Point2d和Point3d的内存布局为:
vptr可能放在class object的开始处,也可能放在class object的结尾处,具体的位置视编译期的实现而定。
3.多重继承
以上是关于Data语意学的主要内容,如果未能解决你的问题,请参考以下文章