The Semantics of Constructors
对于c++的一个最长听到的抱怨就是编译器在幕后帮你做了太多的东西。最常见的就是操作符的自动转换.通过对编译器背后工作的了解,我们可以对c++这个语言进行更深入的思考。
Default Constructor Construction
在C++ Annotated Reference Manual(ARM)中定义:缺省构造函数是由编译器在需要的地方自动生成的。所谓的需要要看具体情况而定。对于程序中需要显性定义的变量,需要程序员自己定义缺省构造函数去初始化,编译器不会自动集成进去缺省构造函数。对于stack或heap中的变量,如果你不去初始化,它会保持先前的状态。而全局变量则会初始化为0值。
有四种情况编译器会生成缺省构造函数
Ø Member Class Object with Default Constructor
如果某个类的成员中有一个类对象是有缺省构造函数的,那么编译器会给这个类自动添加一个缺省构造函数。
一个有趣的问题是: 基于C++分别编译的模式,编译器如何避免添加多个缺省构造函数呢造成的冲突呢。实际上,编译器把自动添加的缺省构造函数,拷贝构造函数和赋值拷贝函数都设为Inline来避免这个问题,使得这些函数只在使用到的文件内部可见。如果这些函数过于复杂,编译器将会添加一个non inline 的static函数来保证不和其他文件冲突。因为这些函数默认只有声明而没有具体实现的代码。因此在实际需要的调用中,编译器会把函数实现集成到当前文件中。
如果类中包含多个有缺省构造函数的类对象,将根据其声明的顺序调用其缺省构造函数。
Ø Base Class with Default Constructor
如果基类有缺省构造函数,而子类没有定义,编译器会自动添加一个。
如果用户定义了多个构造函数,编译器将会自动添加调用基类缺省构造函数的代码到每一个构造函数里。如果其还包含有缺省构造函数的类对象,将会放在基类构造函数之后。
Ø Class with a Virtual Function
两种情况
1. 该类声明了一个虚函数
2. 该类的继承链中有包含虚函数的类
自动添加的内容:
1. 虚函数表(vtable)
2. 指向vtable的vptr.
Ø Class with a Virtual Base Class
不同的编译器的虚基类的实现大不相同。不过,对不同的实现来说相同的是要在运行时确定虚基类的位置。
Summary
C++初学者的两个误区:
1. 没有定义缺省构造函数的类都会自动集成一个
2. 编译器继承的缺省构造函数会自动初始化成员变量
Copy Constructor Construction
在三种情况下会调用拷贝构造函数。
1. 用一个已存在的对象初始化另一个对象。
2. 作为传入参数
3. 作为返回值
后两种情况可能会产生一个临时对象,或者会有编译器优化的代码变换,或者兼而有之。
Ø Default Memberwise Initialization
当用户没有显式定义拷贝构造函数时,编译器将采用Default Memberwise Initilization 来初始化。也就是将包含的成员的内容一一拷贝过去,而对于类对象成员,则递归的应用这个方法。
Ø Bitwise Copy Semantics
位拷贝就是一位位的逐一拷贝。如果你没有显式定义一个拷贝函数,而且你的成员中,也没有需要调用拷贝构造函数的类成员,将会采用这种方式。
这种情况下拷贝构造函数是trival的,编译器不会继承一个实际的拷贝构造函数。
Ø Bitwise Copy Semantis-Not
同样的,四种情况下,不会采用位拷贝的语义:
1. 包含了有拷贝构造函数的类成员
2. 继承自有拷贝构造函数的类
3. 有一个或多个虚函数
4. 继承链中有包含虚函数的类
Ø Resetting the Virtual Table Pointer
主要是用子类来初始化基类时,需要重设vptr,保证指向的是基类的vtable.
Ø Handling the Virtual Base Class Subobject
用了虚继承,就不能用bitwise copy semantics. 具体虚继承的实现机制后面章节再讲。
Program Transformation Semantics
对于一段简单的代码,编译器会做一些变换的工作。这张介绍一些基本的代码变换。
Ø Explicit Initialization
对于拷贝构造的几种情况,经过代码后都是一样的。先定义,然后调用拷贝构造函数。
Ø Argument Initialization
当一个class object 做参数是,会插入一个临时对象,调用其拷贝构造来初始化。而原来的函数也会变成传入引用的形式。在调用函数后要调用对象的析构函数。这是一种策略。另外一种策略就是程序堆栈中生成临时对象,函数退出时自动析构。
Ø Return Initialization
返回值在函数中加一个引用对象参数,返回前拷贝构造返回的对象
Ø Optimization at the User level
直接返回临时对象可以减少开销
Ø Optimization at the Compiler level
NRV(Named Return Value) optimization.如果所有返回值都是同一对象,可能会采用这个优化。
Ø The Copy Constructor: To Have or To Have Not?
一般情况下并不需要,编译器会帮你处理。包含指针(即有外部的资源)的时候可能需要处理。