C++参考手册告诉我们:default constructors
…在需要的时候被编译器产生出来。关键字眼是:在需要的时候。
被谁需要? 做什么事?
当编译器需要它的时候(注意是编译器需要,而不是程序的需要),此外被合成出来的constructor只执行编译器所需要的行为(而不会执行程序所需要的行为,这个设计类的程序员负责)。
C++ standard 规定:
对于class X,如果没有任何user-declared
constructor,那么会有一个default
constructor被暗中(implicitly)声明出来… 一个被暗中声明出来的default constructor将是一个trival constructor.
一个nontrivial default
constructor在ARM(C++ Annotated Reference Manual)的术语中就是编译器所需要的那种,必要的话会被编译器合成出来。
有四种情况需要nontrivial default constructor
1 “带有default constructor”的member class object
如果一个class没有任何构造函数,但它内含一个member class
object,而后者有default
constructor,那么这个class 的implicit defaut
constructor就是“nontrival”,编译器需要为此class合成一个默认构造函数,不过这个合成操作只在构造函数真正需要调用时才会合成。
注意:
1>合成的默认构造函数并不产生任何代码来初始化非类对象成员,这些成员的初始化是程序的责任。即:合成的默认构造函数只满足编译器的需要。
2>如果class A内含一个或一个以上的member class object,那么class A的每一个构造函数必须调用member
class的默认构造函数。如果没有显示调用,编译器会扩展已存在的构造函数,在其中插入一些码,使得user
code执行之前,先调用必要的默认构造函数。
3>C++语言要求以“member objects
在class中声明次序”来调用各个构造函数。这一个有编译器完成,并且在显示用户代码之前完成。
2 “带有一个default constructor function”的base class
如果一个没有任何constructors的class派生自一个“带有一个default constructor
function”的base class,那么这个派生类的默认构造函数会被视为nontrival,并因此被合成出来,它将调用上一层的default
constructor(根据他们声明的次序)。
如果类设计已经定义了多个constructors,编译器会扩张现有的每一个constructors,将“用以调用所有必要之默认构造函数”的程序代码加进去。如果同时亦存在“带有default constructors”的member
class objects,那些default constructor也会被调用-在所有base
class constructors被调用之后,但user
code执行之前。
3 "带有一个virtual function" 的class?
为了让虚函数展现的动态绑定机制发挥出来,编译器必须为每一个基类(或其派生类)对象的vptr设定初始值,放置适当的virtual
table的地址。对于class所定义的每一个constructors,编译器会安插一些代码来做这样的事情。对于那些未声明任何constructors的classes,编译器会为他们合成一个default
constructor,以便正确初始化每一个class object的vptr。
4 “带有一个virtual base class”的class
virtual bass class
的实现法在不同的编译器之间有极大的差异,然而,每一种实现方法的共同点在于必须使virtual base
class在其每一个derived class object中的位置,能够于执行期备妥当。
cfront编译器的做法是靠“在derived class
object的每一个virtual base classes中安插一个指针”完成。所有“经由reference 或
pointer来存取一个virtual base class”的操作都可以通过相关指针完成。
这个指针(或编译器所做出的某个什么东西)是在class
object构建期被完成初始化的。对于class 所定义的每一个constructors,编译器会安插那些“允许每一个virtual base
class的执行期存取操作”的码。如果class没有声明任何constructors,编译器为它合成一个default
constructor.
总结
以上四种情况,会导致“编译器必须为未声明constructor之class合成一个default
constructor”。C++标准把那些合成物称为implicit
nontrivial default
constructor。被合成出来的constructor只能满足编译器(而非程序)的需求。至于没有存在以上四种情况而没有声明任何constructor的class,我们说他们拥有的是implicit
trivial default constructor,他们实际上并没有被合成出来。
在合成出来的default
constructor中,只有base class
subjects和member class object会被初始化,所有其他的nonstatic
data member,如整数、整数指针、整数数组等等都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则并非必要。
copy constructor的建构操作
有三种情况,会以一个object的内容作为另一个class
object的初值。1> 对一个对象做明确的初始化操作(X xx=x;)2>当object被当作参数交给某个函数 3>
当函数传回一个class object时。
假设class设计者明确定义了一个copy
constructor,那么大多数情况下,当一个class
object以另一个同类实体作为初值时,copy
constructor会被调用。
如果class没有提供一个explicit copy
constructor有如何?
1 当class
object以“相同class的另一个object”作为初值时,其内部是以所谓的default
memberwise initialization手法完成的,也就是把每一个内建的或派生的data
member(例如一个指针或一个数组)的值,从某个object拷贝一份到另一个object身上。
不过它并不会拷贝其中的member
class object,而是以递归的方式施行memberwise initialization。
2 这样的操作实际上如何完成?ARM告诉我们:
1> 从概念上而言,对一个class
X,这个操作是被一个copy constructor实现出来...。
2> 一个良好的编译器可以为大部分class
objects产生bitwise copies,因为他们有bitwise copy semantics...。也就是说,"如果一个class未定义出copy
constructor,编译器就自动为它产生出一个”这句话不对。而是应该想ARM所说:
3> default
constructors和copy constructors在必要的时候才由编译器产生出来。“必要”意指class不展现bitwise
copy semantics时。
3 C++标准仍保留了ARM的意义,表述如下:
就像default
constructor一样,C++标准上说,如果class没有声明一个copy constructor,就会隐含的声明(implicitly
declared)或隐含的定义(implicitly defined)出来。和以前一样,C++标准把copy
constructor区别为trivial和nontrivial两种。只有nontrivial的实体才会被合成与程序之中。
决定一个copy
constructor是否为trivial的标准在于class是否展现出所谓的“bitwise copy
semantics"。
在一个类未展现出bitwise copy
semantics,而且没有定义一个显示copy constructor,编译器必须合成出一个copy
constructor以便实施一些bitwise
copy不能完成的任务。
注意:
被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制。
4 什么时候一个class不展现出bitwise copy
semantics呢?有四种情况:
1> 当class内含一个member
object,而后者的class声明有一个copy
constructor时(不论是被class设计者明确地声明,或是是被编译器合成)。
2> 当class继承自一个bass
class而后者存在有一个copy constructor时(再次强调,不论是被明确声明或被合成而得)。
3>
当class声明了一个或多个virtual functions时。这种情况编译器会为每一个class
object安插一个vptr指针,这样该类就不展现bitwise semantics了。现在,编译器要合成一个copy
constructor,以求将vptr适当地初始化。
注意:
①
当一个类对象以另一个相同的类对象作为初值时,都可以直接靠"bitwise copy semantics"完成。
② 当一个base class
object以其derived class的object内容初始化操作时,其vptr复制操作必须保证安全。合成出来的基类copy
constructor会明确设定object的vptr指向基类的virtual table,而不是直接从右手边的派生类中将其vptr现值拷贝过去。
4>
当class派生自一个继承串联,其中有一个或多个virtual base classes时。
virtual base class
的存在需要特别处理。一个class object如果以另一个object作为初值,而后者有一个virtual class
subobject,那么会使"bitwise copy semantics"失效。
每一个编译器对于虚拟继承的支持承诺,都表示必须让“derived class object中的virtual base class
subobject位置”在执行期备妥。维护“位置的完整性”是编译器的责任。bitwise copy
semantics可能会破坏这个位置,所以编译器必须在它自己合成出来的copy
constructor中做出仲裁(一部分工作是安插一些码设定virtual base class
pointer/offset的初值或简单地确定它没有被摸消)。
5 总结
在上面四种情况下,class不再保持“bitwise
copy semantics”,而且copy constructor如果未声明的话,会视为nontrivial。在这四种情况下,如果缺乏一个已声明的copy
constructor,编译器为了正确处理“以一个class
object作为另一个class object的初值”,必须合成出一个copy constructor。
6 copy constructor的应用
copy
constructor迫使编译器多多少少对你的程序代码做部分转化,尤其是当一个函数以传值(by value)方式传回一个class
object,而该class有一个copy constructor(不论是明确定义出来的,或是合成的)时,这将导致深奥的程序转化-不论在函数的定义和使用上。