最近读《深度探索C++对象模型》(下载见 ),满足了自己不少的好奇心。在此主要讨论下默认构造函数(default constructor) 和拷贝构造函数(copy constructor)的问题。
Default Constructor
首先以下几种情况下,编译器是不会自动合成默认构造函数的:
用户定义了其他带参数的构造函数(包括拷贝构造函数) 包含const成员 包含引用成员那么其他情况下,如果用户没有定义任何构造函数,那么编译器是否一定会合成默认构造函数呢?
先看个例子
class Node { public: int ID_; }; Node node; func(node); 003317BE lea eax,[node] 003317C1 push eax 003317C2 call func (331082h) 003317C7 add esp,4 class Node { public: string name_; int ID_; }; Node node; 00104C7E lea ecx,[node] 00104C81 call Node::Node (10131Bh) func(node); 00104C86 lea eax,[node] 00104C89 push eax 00104C8A call func (101082h) 00104C8F add esp,4
以上分别是两个类的定义和同样一段代码反汇编的结果,可以看到第一个例子并没有调用默认构造函数。
ISO C++ 2003有如下描述:
If there is no user-declared constructor for class X, a defaultconstructor is implicitly declared. Animplicitly-declareddefault constructoris an inline public member of its class. A constructor istrivialif it is an implicitly-declareddefault constructor and if:
— its class has no virtualfunctions (10.3) and no virtual base classes (10.1), and
— all the direct base classesof its class have trivial constructors, and
— for all the nonstatic datamembers of its class that are of class type (or array thereof), each such classhas a trivial constructor.
trivial default constructor 是个空的inline函数,而且很可能在生成代码时被优化掉了。
根据上面的描述,如果一个类满足以下条件,则编译器会合成nontrivial default constructor:
1. 声明(或继承)一个虚函数
这种类需要一个虚函数表,因此编译器会在合成的默认构造函数里构造虚函数表,并生成和初始化vtbptr。
2.派生自一个继承链,其中有一个或多个虚基类
编译器对虚基类的处理有很大差异,但其共同点是需要记录虚基类在每个派生类中的位置。
3. 其基类有nontrivial default constructor
根据声明次序调用上一层基类的默认构造函数。
4. 包含带有默认构造函数的member class object
以member在class中的声明次序来调用member的默认构造函数。
在上述情况下,如果设计者已经提供任何形式的构造函数,则编译器会扩张现有的每一个构造函数,
将相关代码加进去,而不会合成新的默认构造函数。
Copy Constructor
类似于默认构造函数,C++标准把拷贝构造函数也分为 trivial和nontrivial两种,只有nontrivial的实体才会被合成于程序之中。
只有class不展现出bitwise copy semantics时,才会合成一个nontrivial的拷贝构造函数.
bitwise copy semantics是指可以通过按位拷贝来复制一个类,那么显然包含指针成员时就不属于这种情形,
包含其他类对象时,则有可能不属于bitwise copy semantics。
具体来说有以下有四种情况不属于bitwise copy semantics:
1.class内含一个成员对象,后者的class声明有一个拷贝构造函数(不论是用户明确声明,还是被编译器合成)
2.当class继承自一基类,而后者存在一个拷贝构造函数
3.当class声明一个或多个虚函数时
4.当class派生自一个继承链,其中有一个或多个虚基类。
上述4种情况与默认构造函数的情况基本一样,执行的操作也类似。
这里我们看到,当一个类不属于上述情况但包含一个指针成员时,编译器仍然会当作bitwise copy semantics来处理,
也就是直接拷贝指针,这也是导致很多bug的原因。