看完了《inside c++ object model》,接下来,就是亲自动手实践验证了。 依然记得大二时候那本清华大学出版社出版的C++教材,我用了2周时间就看完了,那时候的我还没有任何面向对象的思想,更别提项目经验了。感觉整个C++就是把C的struct扩充了一下,又加了点多态机制和泛型编程罢了(事实上,那时候对多态和泛型编程的概念也不是很清楚,只知道virtual和templete)。呵呵。。 而今看来很是自负啊。 事实上那本书虽然简单,但我却看过很多遍,在讲到构造函数时,书中有一条很明确的写到:“当一个类没有构造函数时候,那么编译器就会为它生成一个默认的构造函数”。 在我没有读到Lippman大牛的著作权,或许我会一直以为这是个永恒不变的真理。Lippman对于很多程序员的这一点认识如是评论道:
“C++新手一般有两个常见的误解:
1.任何class如果没有定义default construtor,就会被合成一个出来;
2.编译器合成出来的default constructor会明确设定class内每一个data member”
看看第一条,莫不是说那个写教科书的人也是个C++新手?呵呵。。
Lippman又明确解释道,编译器只有在需要的时候才会自动生成一个default constructor。至于什么时候是必要的时候:符合以下4点就是有必要的时候:
1.带有default constructor的成员class member object;
2.带有default constructor的基类;
3.带有virtual function;
4.带有vitrual base class。
Lippman对于这四点没有给出明确的验证实例,因为要验证一个编译器是否为一个类合成了default constructor,除了从汇编语言的角度来看汇编代码外,貌似没有其它任何方法了。。感叹自己汇编很弱,不能从汇编角度来分析,但幸运的是我在VS2008里面无意中发现了一种验证方法。先来看看如下代码:(这点简单代码能验证上述4个条款)
#include <iostream> using namespace std; class BaseClass { public: int a; int b; //virtual void showClassInfo(){}; //BaseClass(){} }; class DerivedClass:public BaseClass { public: int c; }; int main(int *argc,char **argv) { BaseClass baseClass; //cout<<baseClass.a<<" "<<baseClass.b<<endl; cout<<sizeof(baseClass)<<endl; DerivedClass derivedClass; //cout<<derivedClass.c<<endl; cout<<sizeof(derivedClass)<<endl; return 0; }
得出的结果将会是:8 12
这证明类的nonstatic data member被正确的申请了内存空间。而如果我们去掉:
//cout<<baseClass.a<<" "<<baseClass.b<<endl;
这行的注释,会出现runtime error. 给出的提示信息为:the variable “baseClass”is being used without being initialized.(baseClass未进行初始化),我假设这句话意图在指出这个异常是由于baseClass没有设定默认的构造函数,而并不是因为没有给baseClass.a和baseClass.b赋初值,为了验证这一点,我加了一个什么也没做的构造函数,去掉
//BaseClass(){}
这一行的注释,那么得出的结果会是:
-858993460 -858993460
8
12
此时程序没有任何异常。验证了我之前的假设是对的。构造函数的确没有给baseClass的data member做初始化操作,而却得以正常运行。
如果我们将上述代码中的这一样注释掉:(注意BaseClass的构造函数此时是注释掉的)
//virtual void showClassInfo(){};
这行代码的注释去掉,那么程序会照样正常运行,得出的结果会是:
-858993460 -858993460
12
16
此时baseClass中隐含了一个vptr指向virtual function table,造就了baseClass的重新布局(增加了4个byte)。对于C++对象布局,可以看看我之前的一篇博文“点击这里” 。这也就验证了Lippman所说的第三条。为了验证第四条,我们将上述代码中的这一行:
//cout<<derivedClass.c<<endl;
注释去掉,此时又会出现runtime error.而给出的信息为:the variable “derivedClass”is being used without being initialized.(derivedClass未进行初始化)。证明编译器又没有为非vitual继承的derived class合成默认的构造函数。 那么正常的virtual 继承会出现什么状况呢?再试着将这行代码
class DerivedClass:public BaseClass
改为如下:
class DerivedClass:virtual public BaseClass
(注意//cout<<derivedClass.c<<endl;的注释是去掉的)
程序又将会正常运行,而且得出的结果是:
8
-858993460
16
如此便验证了第四条。
至此,第一条和第二条的验证方法也就没必要再说下去了,大同小异。
最后,不得不感叹国外教科书作家Lippman和国内教科书作家的的差异。。