默认构造函数是C++对象中最基本的一类函数,如果一个类没有自定义任何构造函数,那么编译器会为这个类生成一个 implicit default constructor,只不过这是一个trivial(无能的,无用的) constructor。一个 nontrivial default constructor 在必要的时候会由编译器合成出来,以下是 nontrivial default constructor 的四种情况。
如果一个类没有任何构造器,但它内含一个成员对象,这个成员对象拥有默认的构造器,那么这个类的隐式默认构造函数就是“nontrivial”,编译器需要为这个类合成出一个默认构造函数,不过这个合成操作只有在constructor真正需要被调用时才会发生。
由此衍生出一个问题,即如果在一个头文件中声明了一个类,如果这个头文件被多个cpp文件包括,那么编译器是如何避免重复定义默认构造函数呢?解决方法是把合成的 default constructor、copy constructor、destructor、assignment copy constructor 都以inline方式完成。因为一个inline函数有静态链接(static linkage),不会被文件以外者看到。
例如:
class Foo{
public:
Foo(){}
Foo(int) {...}
};
class Bar{
public:
Foo foo;
char *str;
};
void foo_bar(){
Bar bar; // bar中的foo必须初始化,因为foo是一个成员对象,而其class Foo拥有默认构造器,符合本节主题
if(str){...}
}
被合成的 Bar 的 default construction 内含必要的代码,能够调用 class Foo 的 default constructor 来处理成员变量 Bar::foo,但它并不产生任何代码来初始化 Bar::str,因为将 str 初始化是程序员的责任。
被合成出的 default constructor 可能是这样:
//Bar 的 default constructor 可能会被这样合成
// 为成员的 foo 调用 class Foo 的 defalut constructor
inline
Bar::Bar(){
//c++伪码
foo.Foo::Foo();
}
注意,合成的 default constructor 只满足编译器的需要,而不是程序员的需要。为了让这个程序片段能够正常执行,我们还需要 对 str 进行初始化,假设我们自己定义了一个 Bar 的 default constructor 如下:
Bar::Bar(){
str = 0;
}
现在我们的要求被满足了,但是对编译器来说,foo 仍然需要初始化,又因为 default constructor 已经被显式地定义出来,编译器没有办法合成第二个,因为编译器的行动会是:
如果一个 class A 内含一个或者多个 member class objects,那么 class A 的每一个 constructor 必须调用每一个 member class 的 default constructor。编译器会扩张已经存在的 constructors,在其中安插一些代码,使得 user code 被执行之前,先调用必要的 default constructors。
因此,上述的 default constructor 可能被编译器扩张为
Bar::Bar(){
foo.Foo::Foo();
str = 0;
}
如果有多个 class member objects 都要求 constructor 初始化操作,C++语言要求以“member objects 在 class 中声明顺序”来调用各个 constructors。这一点由编译器完成,这些代码将被安插在 explicit user code 之前。例如:
class Dopey{ public: Dopey(); };
class Sneezy{ public: Sneezy(); Sneezy(int); };
class Bashful{ public: Bashful(); };
class SnowWhite{
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
// ...
private:
int number;
};
如果 SnowWhite没有定义 default constructor,就会有一个 nontrivial constructor 被合成出来,依序调用 Dopey、Sneezy、Bashful 的 default constructors。然而如果 SnowWhite 定义了下面这样的 default constructor:
SnowWhite::SnowWhite() : sneezy(1024){
number = 2048;
}
它会被扩张为:
SnowWhite::SnowWhite() : sneezy(1024){
//插入member class object
//调用其 constructor
depoy.Depoy::Depoy();
sneezy.Sneezy::Sneezy(1024);
bashful.Bashful::Bashful();
//explicit user code
number = 2048;
}
类似的道理,如果一个没有任何构造器的 class 派生自一个带有默认构造器的 base class,那么这个派生类的默认构造器也会被视为 nontrivial,并因此需要被合成出来。它将调用上一层 base classes 的默认构造器(根据它们的声明顺序)。对于一个后继派生的 class 而言,这个合成的构造器和一个“被显式提供的默认构造器”没有什么差异。
如果设计者提供多个构造器,但其中都没有默认构造器,则编译器会扩张现有的每一个构造器,将其调用所有必要之默认构造器的代码加进去。它不会合成一个新的默认构造器。
如果同时存在成员对象,则成员对象的默认构造器也会被调用——在所有 base class 的构造器被调用之后。
例如:
class Widget{
public:
virtual void flip() = 0;
};
void flip(const Widget& widget){
widget.flip();
}
void foo(){
//假设 Bell 和 Whistle 都派生自 Widget
Bell bell;
Whistle whistle;
flip(bell);
flip(whistle);
}
下面两个扩张行动会在编译期间发生:
1.一个 virtual function table(vtbl)会被编译器产生出来,内放在 class 的 virtual functions 地址。
2.在每一个 class object 中,一个额外的 pointer member(也就是 vptr)会被编译器合成出来,内含相关之 class vtbl 的地址
对于 class 中定义每个构造器,编译器都会安插一些代码来做这样的事情。对于那些未声明任何构造器的 classes,编译器都会为它们合成一个 default constructor,以便正确地初始化每一个 class object 的 vptr。
如下:
class X { public: int i; };
class A : public virtual X { public: int j; };
class B : public virtual X { public: double d; };
class C : public A, public B { public: int k; };
//无法在编译时期确定(resolve)出 pa->X::i 的位置
void foo(const A* pa){
pa->i = 1024;
}
main(){
foo(new A);
foo(new C);
//...
}
编译器无法固定住 foo() 之中“经由 pa 而存取的 X::i ”的实际偏移位置,因为 pa 的真正类型可以被改变。编译器必须改变“执行存取操作”的那些代码,使 X::i 可以延迟至执行期才决定下来。原先 cfront 的做法是靠 “在 derived class object 的每一个 virtual base class 中安插一个指针” 完成。所有 “经由 reference 或 pointer 来存取一个 virtual base class” 的操作都可以通过相关指针完成。在上面的例子中,foo() 可以被改下如下:
void foo(const A* pa){
pa->___vbcX->i = 1024;
}
其中__vbcX 表示编译器所产生的指针,指向 virtual base class X,而这个指针就是在 class object 构造期间被完成的。即如果没有任何构造器,编译器必须为它合成一个默认构造器。
有4种情况,会造成 ”编译器必须为未声明构造器的类合成一个默认构造器”,即 implicit nontrivial default constructors。被合成出来的构造器只满足编译器的需要。它之所以能够完成任务,是借着 “调用 member object 或 base class 的default constructor” 或是 “为每一个 object 初始化其 virtual function 机制或 virtual base class 机制” 而完成的。
至于没有存在这4种情况而又没有声明任何构造器的类,它们拥有的是 implicit trivial default constructors,它们实际上不会被合成出来。
在合成的 default constructor 中,只有 base class subobjects 和 member class objects 会被初始化。所有其他的 nonstatic data member 都不会被初始化。
以上类容总结于《深度探索C++对象模型》