默认构造函数既可以是用户自定义的,也可以是编译器合成出来的。编译器在需要的时候才会合成有意义的构造函数,其他时候合成构造函数不执行任何操作(C++对象模型里称此时实际上没有合成出来)。合成的默认构造函数是否有实际意义由是否是编译器需要而定。
1、非编译器需要
类的数据成员是内置类型或合成类型,此时合成的默认构造函数是没有意义的,a和b的值将是内存上次被使用后的遗迹。
class A
{
private:
int a;
int* b;
}
2、编译器需要分为五种情况:类的数据成员是某个类的对象,且该类定义了默认构造函数;派生类的基类定义了默认构造函数;类声明或者继承了一个虚函数;类派生自一个含有虚基类的继承树;派生类虚继承于基类。此时合成的默认构造函数是有意义的。
情况一和二:
class A{
public:
A() { a = 1; }; // 默认构造函数
private:
int a;
}
class B{
private:
A a; // 类的数据成员是类A的对象
}
// 类C派生于A,A含有默认构造函数
class C : public A { }
情况三、四:
class A{
public:
virtual void virfun() = 0;
}
// 编译器会在每个B对象里合成一个虚表指针
// 合成的默认构造函数会处理虚表指针,使其指向虚函数表
class B : public A{ }
情况五:
class X { public: int i; }
class A : virtual public X { }
class B : virtual public X { }
class C : public A, public B {}
3、编译器需要和非编译器需要混合情况
class A{
public:
A() { a = 1; }; // 默认构造函数
private:
int a;
}
class B{
private:
A a; // 类的数据成员是类A的对象
int b; // b不会被初始化
}
4、自定义默认构造函数
class A{
public:
A() { a = 1; }; // 默认构造函数
private:
int a;
};
class B{
public:
// 虽然定义了默认构造函数,且没有初始化a
// a依旧可以用自己的默认构造函数初始化
B() { b = 2; }
private:
A a;
int b;
};
5、未定义默认构造函数如果已经定义了其他构造函数,编译器不会再合成默认构造函数,此时建议定义一个默认构造函数。
拷贝构造函数有三个使用场景:
如果class没有声明一个拷贝构造函数,就会合成拷贝构造函数,如果class只做bitwise copy操作,合成的拷贝构造函数是没有意义的(C++对象模型说其其实没有被合成);只有class不做bitwise copy操作,合成的拷贝构造函数才是有意的,真的被合成出来。有如下情况:
构造函数初始化列表在构造函数体之前执行,负责类数据成员的初始化。如果没有定义初始化列表,在构造函数体执行会存在效率问题——此时相当于先使用初始化列表默认初始化数据成员,然后在函数体内执行拷贝操作。并且以下情况必须要列表初始化:
这里要注意一点,我们自己定义初始化列表时有可能不会在意其初始化顺序,实际上,类成员函数在初始化列表中初始化顺序是依照其在类定义中出现的顺序,基类数据成员的初始化早于派生类数据成员。这也就意味着,初始化列表中的数据成员的书写顺序和数据成员的真正初始化顺序无关,这是很重要的。看下面一段代码:
class X{
public:
X(int val) : j(val), i(j) {} //i的值未知
private:
int i;
int j;
};
编译器会对初始化列表一一处理,并可能重新排序,以反映出数据成员的声明次序,它会安插一些代码到构造函数体内,并置于任何构造函数内用户定义代码之前。