1. 类对象大小受三方面的影响:1)virtual base或virtual func带来的负担;2)是否EBO;3)alignment(sizeof大全)。
2. virtual base class表示“只有一个单一而被共享的实体”,而无论出现在继承体系中多少次。
3. c++并不强制“base class subobject的内存排列次序”和“不同存取级别的类中的数据成员”的排列次序。
4. static数据成员会放到global data segment中,并不会影响到单独某一个object的大小。
5. 数据成员类型的绑定、成员函数指针请参看代码部分。关于成员函数指针,要对对象的sizeof非常了解才能懂。
6. 由X的对象x和指针pt存取成员变量m,何时会有什么重大差别?当m是从X的virtual基类派生而来时。
7. 内存中,基类出现在子类前面,但virtual基类会在整个布局的最后面;如果有多个virtual基类,以声明序排列。
8. 继承对类的数据成员的影响(详细请看系列的附录部分):
a) 简单无多态的继承。不会增加内存空间以及存取上的额外负担。C++语言保证:出现在子类中的基类子对象(base class subobject)要保持其原样完整性”,即:任何时间取出“基类子对象”都是一个完整的整体。要注意对齐问题。
b) 加上多态(有虚函数)
Ø 开销:1)vtbl(含typeid);2)加入vptr;改造3)构造函数和4)析构函数,以处理vptr。VC把vptr放到对象的最前面(如果有virtual base class的话,vptr会在offset指针之后),即使基类没有virtual函数而只有子类有。
Ø 子类与第一个父共享同一个vptr,有第二基类会增加vptr。子类增加新或修改virtual函数会更新对应vtbl的内容。
c) 多重继承:其问题主要出现在派生类对象与其第二或后面的基类对象之间的转换,因为这样会涉及到指针的offset。
d) 虚拟继承即类含有N个virtual基类
Ø 代表着它可以被切割为两部分:不变部分(放前面)和共享部分(放后面)。
Ø 不变部分中的数据,不论后继如何变化,其offset总是固定的;而共享部分,表示的是virtual基类的子对象,它的offset会因为派生层次的增加(无论在最底层还是最高层增加)而有变化,因为它总是在整个类对象的最后面。
Ø 派生类中会安排指向virtual基类的offset指针,与访问virtual基类成员,需要首先进行offset进行偏移定位。
9. 关于对齐对sizeof的影响,只有:1)所有的“基类子对象”全是char;或2)virtual base全是char;这两种情况才可能出现奇数size(19,23等),其他情况肯定都是被对齐到int、*(比如vptr)、double等的长度。
一些实验代码:
#ifndef ch3_h #define ch3_h namespace ch3{ // Data semantic // 本章其他部分的代码,请参见另外的工程:sizeoftest.dsw // p96 namespace static_inherit{ // 父类和子类共享同一个xx对象,一个修改了,另外一个也被修改 class cls{ public: static int xx; }; int cls::xx = 8; class der: public cls {}; void test(){ cls c1; c1.xx = 10; der d1; d1.xx = 20; } } // p90 namespace global_cls_var{ // 在cls中,如果有和global同名的变量,那么优先使用类中的变量 // 也就是说,整个程序会首先把类分析完成之后,才开始对类的成员 // 函数等的分析 int mytype; // No1= class cls{ public: float get(){ return mytype;}; public: float mytype; // No2 = No1,名字一样。 No2优先于No1 }; void test(){ mytype = 8; cls c; c.mytype = 2; int tmp = (int)c.get(); if ( tmp == mytype) { // 全局优先 printf("变量选择全局优先/n"); } else if ( tmp == c.mytype) { // 局部优先 printf("变量选择局部优先/n"); } else { printf("不可能,奇怪了/n"); } } } // p91 namespace type_def{ //A: 对于类的函数的参数列表中的类型,或者函数返回值类型: // 1)在cls中,typedef的定义在mytype的使用之后, // 会编译错误; // 2)如果把cls中的typedef的定义在mytype的使用之前的话, // 程序优先选择类内部的的typedef //B: 对于类的成员变量来说,永远使用global的typedef。 // 而且,无论局部的typedef在其前还是后,都不会编译错误。 // 这个,使用起来非常麻烦,要注意。 // 我觉得,根本的避免方法就是:不要定义同名的东西, // 省得自找麻烦 typedef int mytype; // N0.1 class test{public:float f;}; class cls{//局部typedef定义比使用靠前 public: // begin ---> // 下面的定义,是合法的。 // 说明:从global typedef --> local typedef --> mem func , // 这样的一个过程下来之后,class内部的typedef就会被优先于global而使用了 typedef test mytype; // N0.2 ===== N0.1,类型不一样 // 注意:m->f的用法,说明这个m肯定是一个test类型,而不是int类型 void test(mytype* m){m->f = 0; }; //函数参数在定义之后 // end <<---- int n; mytype p; //变量使用在定义之后 }; class cls2{//局部typedef定义比使用靠后 public: int n; mytype p; //变量使用在定义之前 void test(mytype* m){ }; //函数参数在定义之前 // 下面的定义如果出现的话,就会出现如下的error: // error C2327: 'cls2::test' : member from enclosing class // is not a type name, static, or enumerator // 说明:从global typedef --> mem func --> local typedef, // 这样的一个过程下来之后,class内部的typedef就会被认为是不合法了 //typedef test mytype; // N0.3 ===== N0.1,类型不一样 }; void test(){ // 下面的两个输出都是“global works” // cls的长度是int + mytype. // mytype == int(4)?还是mytype == test(8) int n = sizeof(cls); if ( n == sizeof(int) * 2) { //mytype == int(4) printf("cls: typedef global works/n"); } else { //mytype == float(8) printf("cls: typedef class inner works/n"); } // cls2的长度是int + mytype. // mytype == int(4)?还是mytype == test(8) n = sizeof(cls2); if ( n == sizeof(int) * 2) { //mytype == int(4) printf("cls2: typedef global works/n"); } else { //mytype == float(8) printf("cls2: typedef class inner works/n"); } } } // main test void test(){ printf("ch3 begin/n"); static_inherit::test(); global_cls_var::test(); type_def::test(); printf("ch3 end/n"); } }; #endif