第三章(The Semantics of Data)
一个空的class的大小并不为0;
class x { };//sizeof(x) = 1;
即空的class并不是真的空,它有一个隐晦的1bytes,那是编译器安插进去的一个char,这使得这个class的两个objects得以在内存中配置独一无二的地址:
X a, b;
if(&a == &b) cout << "error" << endl;
class的大小受到三个因素的影响:
- 语言本身所造成的额外负担,virtual(vptr)
- 编译器对于特殊情况所提供的优化处理(比如空class中插入一个char)
- Alignment的限制(对齐的限制)
Point3d origin, *pt = &origin;
(1)origin.x = 0.0;
(2)pt->x = 0.0
那么通过origin存取和通过pt存取,有差别吗?答案是有
每一个nonstatic data member的偏移量(offset)在编译时期即可获知,甚至如果member属于一个base class subobject也奕扬,因此存取一个nonstatic data member,其效率和存取一个C struct member获一个nonderived class的member是一样的。(1)可以确定编译期间就可以知道其是origin的x,固定的;(2)如果x是从virtual base class继承来的,那么pt指向哪一个class type?所以这个操作必须延迟到执行期,(vptr)才能知道。
静态成员在使用前必须要进行初始化,在class内进行定义,在class外进行初始化
class A{
public:
static int x;
};
int A::x = 1;
注意初始化的方式,程序中使用使,使用A::x即可,如果考虑继承或者虚继承,也
永远记住:静态成员只有唯一一个实体(如果有继承不管使用base::x还是driverd::x得到值都是一个值
)
容易犯的一个错误是,把一个class分解为两层或更多层,有可能会膨胀所需空间。
多态的开销
多态会带来的开销:
- 每个 class有一个与之相关的vitual table(虚函数表),用来存放它所声明的每一个virtual functions的地址, table的元素数目通常是virtual functions的数目
- 在每一个class object中导入一个vptr,提供执行期间的链接,使每个object能够找到相应的virtual table
- 加强constructor函数,使它能够为vptr设定初值
- 加强destructor函数
对于单继承,base class和derived class的objects都是从相同的地址开始,其间差异在于derived object比较大,用以容纳它自己的nonstatic data members,
dervied A;
base *B = &A;让基类指针指向派生类很自然。但是如果base中没有虚函数,而derived中有虚函数,那么这种规则就可能被打破,因为derived object中有一个vptr,vptr的插入可能会打破内存布局,那么此时进行转换时,就需要编译器的介入。多重继承更是打破这种布局。