首页 > 代码库 > 构造函数语意学

构造函数语意学

    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(不论是明确定义出来的,或是合成的)时,这将导致深奥的程序转化-不论在函数的定义和使用上。