Bootstrap

C++对象的构造

C++中通过用户自定义类建立对象时,需要调用构造函数,这里包含默认构造函数、复制构造函数和自定义构造函数。其中自定义构造函数,按照函数重载机制进行匹配调用,与普通重载函数调用类似。因此,这里讨论的是默认构造函数和复制构造函数,因为这两种构造函数如果用户不显式定义,会在特定情况下被编译器合成出来。编译器合成的规则并不明显,C++标准做的说明只是“在需要的时候合成”。

默认构造函数(default constructor)

默认构造函数就是不需要参数的构造函数或者参数都带有默认值的构造函数。
理解默认构造函数需要从程序语义层面和编译器层面(C++实现)两个方面来入手。考虑如下代码:

class A{
    int a;
    float b;
};

A objA;
...

从程序语义层面上看,变量objA需要一个默认构造函数,并且最好能初始化成员变量的值为0,但是由于没有明确定义默认构造函数,因此编译器会是否合成出这个默认构造函数取决于编译器层面上能否进行编译,因此就衍生出了trivial和non-trivial默认构造函数的概念,如果是一个non-trivial性质的,编译器就会合成;否则就不会合成。
编译器总共支持四种non-trivial的情形,出现这些情形中的任何一个或多个都会当做non-trivial从而合成出默认构造函数:
1. 类的成员对象存在默认构造函数
2. 类的基类存在默认构造函数
3. 存在虚函数的类
4. 存在虚基类的类

类的成员对象含有默认构造函数

编译器合成的版本的唯一功能就是调用所有这些对象的默认构造函数,这就是编译器必须要满足的要求,其他的初始化操作都不会施行。

class A{
public: A():val(0){}
private: int val;
};
class D : A{
    A a;
    int dval;
};

D objD;

对象 objD 会被调用编译器合成的构造函数,用来唯一功能就是调用成员对象a的默认构造函数。对于用户定义的其他构造函数,如果没有初始化相应的带有默认构造函数的成员对象,编译器也会合成相应的初始化调用语句插入到用户定义的构造函数内最开始处。如果存在多个需要初始化的成员对象,那么会按照类中成员对象声明的顺序来依次调用每个成员的构造函数。

类的基类存在默认构造函数

如果基类存在用户定义的默认构造函数,而子类没有定义默认构造函数时,编译器会合成non-trivial的默认构造函数。对于多个基类的继承,会按照声明顺序依次调用父类的默认构造函数。如果存在用户定义的构造函数而没有默认构造函数,那么会被编译器插入到所有这些构造函数最开始初。另外,如果同时存在有默认构造函数的成员对象,会在调用完父类的构造函数之后,再调用成员对象的构造函数。

存在虚函数的类

对于存在多态性质的类,根据C++多态的实现方式,编译器会为每个对象插入一个虚函数表指针(vptr),因此默认构造函数必须进行这个vptr成员的初始化,放置虚函数表的地址,此时non-trivial默认构造函数必须被合成,插入相应代码来完成这些工作。

class B{
public:
    virtual void fun();
    ...
};
class A:B{
public:
    virtual void fun();
    ...
};

B * pb = new A;
pb->fun();

上述用new创建类A的对象时会调用默认构造函数,由于多态,默认构造函数被编译器合成后初始化虚函数表指针vptr,最终的调用操作会被解析到:

(pb->vptr[1])(pb); //假设虚函数fun为声明的第一个虚函数,地址在在虚函数表中第二项

存在虚基类的类

与前面多态性质的类对象存在vptr类似,虚基类的支持虽然不同编译器有差异,但是共通点都是需要解决虚基类在派生类中的位置能在执行期明确,为了实现这个需求,要么继续使用vptr,要么使用虚基类指针vbc,总之都需要编译器为每个对象合成出新的指针成员,这样就和前面多态的情况类似,需要构造默认构造函数,安插对合成的指针成员的初始化操作。

除了上述四种情况外,其他情况下,如果用户没有定义默认构造函数,编译器并不会合成,因此
1. 不是所有默认构造函数没有定义的类编译器都会合成
2. 编译器合成的默认构造函数不会初始化所有数据成员

复制构造函数(copy constructor)

复制构造函数的调用有三个地方
1. 显式调用
2. 非引用的传递参数
3. 非引用的函数返回

最基本的复制构造就是基于bitwise语义,也就是基本变量的赋值操作。编译器是否合成复制构造函数也是看trivial和non-trivial,而这取决于类是否表现出bitwise拷贝语义,与前面的默认构造函数类似,存在以下四种情况不表现出bitwise拷贝语义,从而需要合成:
1. 成员对象存在复制构造函数
2. 基类存在复制构造函数
3. 多态性的虚函数
4. 带有虚基类

可以很明显看出,这四种与前面默认构造函数的non-trivial完全相同。

;