读书笔记《Inside the C++ Object Model》:Default Constructor的构造操作
来源:互联网 发布:柠檬tv网络电视 编辑:程序博客网 时间:2024/06/10 01:28
C++新手一般有两个常见的误解:
1.任何class如果没有定义default constructor,就会被合成出一个来。
2.编译器合成出来的default constructor会显示设定“class内的每一个data member的默认值”。
实际上,没有一个是真的!
class Foo {public:int val;Foo *pnext;};void foo_bar(){Foo bar;if (bar.val || bar.pnext) {cout << "no init bar members" << endl;} else {cout << "init bar members" << endl;}}
在VC2013中测试结果编译报错:使用了未初始化的局部变量“bar”。
在这个例子中,正确的程序语义是要求Foo有一个default constructor,可以将它的两个members初始化为0。上面这段代码可曾符合所说的“在需要的时候”?答案是NO。其间的差别在于一个是程序的需要,一个是编译器的需要。程序如果有需要,那是程序员的责任。本例中要承担责任的是设计class Foo的人。所以上述代码编译器并不会合成出一个default constructor。
但如果把bar定义为global,VC2013中测试正常编译通过,运行结果:init bar members
Global objects的内存保证会在程序启动的时候被清为0。Local objects配置于程序的堆栈中,heap objects配置于自由空间中,都不一定会被清0,它们的内容将是内存上次被使用后的遗迹。这点和C语言的机制相同。
什么时候编译器才会合成一个default constructor呢?
当编译器需要它的时候!此外,被合成出来的constructor只执行编译器所需要的行动。也就是说,即使有需要为class Foo合成一个default constructor,那个constructor也不会将两个data members val和pnext初始化为0。为了让上一段代码正确编译执行,class Foo的设计者必须提供一个显示的default constructor,将两个members适当地初始化。
C++ Standard已经修改了ARM中的说法,虽然其行为事实上仍然是相同的。C++ Standard[ISO-C++95]的Section 12.1这么说:
对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被隐式声明出来……一个隐式声明出来的default constructor将是一个trivial(浅薄而无能,没啥用的)constructor……
C++ Standard然后开始一一叙述什么情况下这个implicit default constructor会被视为trivial。一个nontrivial default constructor在ARM的术语中就是编译器所需要的那种,必要的话由编译器合成出来。
nontrivial default constructor有下面4种情况。
1.“带有Default Constructor”的Member Class Object
如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是“nontrivial”,编译器需要为该class合成出一个default constructor。不过这个合成操作只有在constructor真正需要被调用时才会发生。
看个例子,在下面程序中,编译器为class Bar合成一个default constructor:
class Foo {public://class Foo拥有default constructorFoo() {cout << "Foo default constructor" << endl;}Foo(int) {cout << "Foo constructor" << endl;}};class Bar {public:Foo foo;//foo是一个member objectchar *str;};void foo_bar(){Bar bar;//Bar::foo是一个member object,而其class Foo拥有default constructorif (bar.str) {cout << "bar.str is not nullptr" << endl;}}被合成的Bar default constructor内含必要的代码,能够调用class Foo的default constructor来处理member object Bar::foo,但它并不产生任何代码来初始化Bar::str。将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。被合成的default constructor看起来可能像这样:
//Bar的default constructor可能会被这样合成//为member foo调用class Foo的default constructorinline Bar::bar(){ //C++伪代码 foo.Foo::Foo();}再一次要注意,被合成的default constructor之满足编译的需要,而不是程序的需要。为了让这个程序能够正确执行,字符指针str也需要被初始化。假设程序员经由下面的default constructor提供了str的初始化操作:
//程序员定义的default constructorBar::Bar(){ str = 0;}现在程序的需求获得满足了,但是编译器还需要初始化member object foo。由于default constructor已经被显示地定义出来,编译器就没有办法合成第二个。这种情况下,编译器会采取什么措施呢?
编译器采取的措施是:“如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor必须调用每一个member class的default constructor”。编译器会扩张已存在的constructor,在其中安插一些代码,使得user code被执行之前,先调用必要的default constructor。扩张后的constructor可能像这样:
//扩张后的default constructor//C++伪代码Bar::Bar(){ foo.Foo::Foo();//附加上的compiler code str = 0;//explicit user code }如果有多个class member objects都要求constructor初始化擦做,C++要求以“member objects在class中的声明顺序”来调用各个constructors。这一点由编译器完成,它为每一个constructor安插程序代码,以“member声明顺序”调用每一个member所管理的default constructor。这些代码将被安插在explicit user code之前。
举例:
class Dopey {public:Dopey();};class Sneezy {public:Sneezy(int);Sneezy();};class Bashful {public:Bashful();};class Snow_White {public://dopey、sneezy和bashful是三个member objectsDopey dopey;Sneezy sneezy;Bashful bashful;private:int mumble;};如果Snow_White没有定义default constructor,就会有一个nontrivial constructor被合成出来,依次调用Dopey、Sneezy、Bashful的default constructors。然后如果Snow_White定义了下面这样的default constructor:
//程序员定义的explicit default constructorSnow_White::Snow_White() : sneezy(1024){ mumble = 2048;}它会被扩张为:
//编译器扩张后的default constructor//C++伪代码Snow_White::Snow_White() : sneezy(1024){ //编译器插入调用member class objects的constructor dopey.Dopey::Dopey(); sneezy.Sneezy::Sneezy(1024); bashful.Bashful::Bashful(); //explicit user code mumble = 2048;}2.“带有Default Constructor”的Base Class
如果一个没有任何constructors的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。它将调用上一层base classes的default constructor(根据它们的声明顺序)。对于一个后继派生的class而言,这个合成的constructor和一个“被显示提供的default constructor”么有什么差异。
如果设计者提供多个constructors,但其中都没有default constructor呢?编译器不会再合成一个新的default constructor,但编译器会扩张现有的每一个constructors,将“用以调用所有必要之default constructors”的程序代码加进去。如果同时也存在着“带有default constructor”的member class objects,那些default constructor也会被调用——在所有base class constructor都被调用之后。
3.“带有一个Virtual Function”的Class
另有两种情况,也需要合成出default constructor:
1.class声明(或继承)一个virtual function。
2.class派生自一个继承串链,其中有一个或更多的virtual base classes。
下面两个扩张行动会在编译期间发生:
1.一个virtual function table(vtbl)会被编译器产生出来,内放class的virtual functions地址。
2.在每一个class object中,一个额外的pointer member(也就是vptr)会被编译器合成出来,内含相关之class vtbl的地址。
为了让多态机制发挥功效,编译器必须为每一个Base(或其派生类的)object的vptr设定初值,放置适当的virtual table地址。对于class所定义的每一个constructor,编译器会安插一些代码来做这样的事情。对于那些未声明任何constructor的classes,编译器会为它们合成一个default constructor,以便正确地初始化每一个class object的vptr。
4.“带有一个Virtual Base Class”的Class
virtual base class的实现法在不同的编译器之间有极大的差异。然而,每一种实现法的共同点在于必须使virtual base class在其每一个derived class object中的位置,能够于执行期准备妥当。
对于class所定义的每一个constructor,编译器会安插那些“允许每一个virtual base class的执行期存取操作”的代码。如果class没有声明任何constructor,编译器必须为它合成一个default constructor。
总结:
有4种情况,会造成“编译器必须为未声明constructor的class合成一个default constructor”。C++ Standard把那些合成物成为implicit nontrivial default constructor。但,被合成出来的default constructor只能够满足编译器(而非程序)的需要。至于没有存在那4种情况而又没有声明任何constructor的classes,我们说它们拥有的是implicit trivial default constructor,它们实际上并不会被合成出来。
在合成的default constructor中,只有base class subobjects和member class objects会被初始化。所有其他的nonstatic data members(如整数、整数指针、整数数组等等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要。如果程序需要一个“把某指针设为0”的default constructor,那么提供它的人应该是程序员。
- 读书笔记《Inside the C++ Object Model》:Default Constructor的构造操作
- 读书笔记《Inside the C++ Object Model》:Copy Constructor的构造操作
- Inside the C++ Model第二讲之 Default Constructor的构造操作
- Inside the c++ object model读书笔记之默认构造函数
- Inside the C++ Model第二讲之 Copy Constructor的构造操作
- inside the c++ object model 读书笔记(第一章)
- Inside The C++ Object Model 读书笔记
- Inside the C++ Object Model 读书笔记
- Inside The c++ Object Model 读书笔记
- 《Inside C++ Object Model》 Chapter 2 : The semantics of Constructor
- Inside the c++ object model - Copy Constructor Construction
- Inside the c++ object model读书笔记之拷贝构造函数(一)
- Inside the c++ object model读书笔记之拷贝构造函数(二)
- Inside the c++ object model读书笔记之拷贝构造函数(三)
- C++ Object Model: Default Constructor
- 2.1 Default Constructor的构造操作
- inside the c++ object model 读书笔记(第二章)
- Inside the c++ object model读书笔记之程序转换
- angular中的http请求封装
- 【DP专辑】ACM动态规划总结
- 支付宝小程序体验—— 业务组件
- 【读书笔记】windows核心编程
- 关于集群、负载均衡、分布式的区别
- 读书笔记《Inside the C++ Object Model》:Default Constructor的构造操作
- Unity 简单手机小游戏
- 58. Length of Last Word
- scrapy的重要对象Requests,Responses的使用
- 单例模式 懒加载和惰性加载
- node.js全局安装和本地安装的区别
- 优秀博客
- 更改版本并下载APK
- 金蝶K3 Wise版本BOM批量单级展开特定BOM数据的代码