一、Default Constructor
C++新手一般有两个误解:
- 任何class如果没有定义default constructor,就会被合成出一个来。
- 编译器合成出来的default constructor会明确设定”class内每一个data member的默认值”。
有四种情况,会导致“编译器必须为未声明constructor之classes合成一个default constructor“。C++ Standard把那些合成物称为implicit nontrivial default constructors(隐式并有价值的默认构造函数)。至于没有存在那四种情况而又没有声明任何constructor的classess,则说它们拥有的是implicit trivial default constructors,它们实际上并不会被合成出来。那么这四种有价值的默认构造函数分别是:
- 带有Default Constructor的Member Class Object。
- 带有Default Constructor的Base Class。
- 带有一个Virtual Function的Class。
- 带有一个Virtual Base Class的Class(Virtual Base Class是用来被虚拟继承的基类,防止在一个对象中重复出现几个基类)。
对于”带有Default Constructor的Member Class Object”,编译器会扩张已存在的construtors,没有则会合成只满足编译器需要的default constructor。然后在constructors中安插一些代码,使得user code在被执行之前,先调用必要的member class objects’ construtors,有多个member class objects则按照在class中声明的次序进行初始化。
对于”带有Default Constructor的Base Class”,含有多个时仍会按照声明的次序进行base class的初始化,如果同时存在着”带有Default Constructor的Member Class Object”,则member class objects的default constructor也会在base class constructor都被调用之后再调用。
对于”带有一个Virtual Function或Virtual Base Class的Class如何初始化vptr”以后再补充
在合成的default constructor中,只有base class subojects和member class objects会被初始化。所有其它的nonstatic data member,如整数、整数指针、整数数组等等都不会被初始化。
二、Copy Constructor
有三种情况,会以一个object的内容作为另一个class object的初值。
- 赋值的方式创建对象。如 X x; X xx = x;
- 值参数传递。如 X xx; foo( xx );
- 函数返回值。如 X foo() { X xx; …; return xx; }
如果class没有提供一个explicit copy constructor又当如何?当class object以“相同class的另一个object“作为初值时,其内部是以所谓的default memberwise initialization手法完成的,也就是把每一个内建或派生的data member的值,从某个object拷贝一份到另一个object身上。不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。
和以前一样,C++ Standard把copy constructor区分为trivial和nontrivial两种。只有nontrivial的实体才会被合成于程序之中,并执行default memeberwise initialization。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的”bitwise copy semantics”,顾名思义,若展现出bitwise copy semantics则不合成default copy constructor,不执行default memberwise initialization,而执行以bit的方式拷贝对象成员数据。
什么时候一个class不展现出”bitwise copy semantics”呢?其实这个与default constructor的4种情况基本是一样的:
- 当class内含一个member object而后者的class声明有一个copy constructor时(不论是被class设计者明确地声明的,或是被编译器合成的)。
- 当class继承自一个base class而后者存在有一个copy constructor时(再次强调,不论是被明确声明还是被合成而得的)。
- 当class声明了一个或多个virtual functions时。
- 当class派生自一个继承串链,其中有一个或多个virtual base classes时。
关于其中的Virtual Table指针的设定,在编译期间有两个程序扩张操作(只要有一个class声明了一个或多个virtual functions就会如此):
- 增加一个virtual function table(vtbl),内含每一个有作用的virtual function的地址。
- 将一个指向virtual function table的指针(vptr),安插在每一个class object内。
当编译器导入一个vptr到class之中时,该class就不再展现bitwise semantics了。现在,编译器需要合成出一个copy constructor,以求将vptr适当地初始化,例子如下 :
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
//...
private:
// ZooAnimal的animate()和draw()
// 所需要的数据
};
class Bear : public ZooAnimal {
public:
Bear();
void animate();
void draw();
virtual void dance();
//...
private:
// Bear的animate()和draw()和dance()
// 所需要的数据
};
上面的代码中Bear派生于ZooAnimal,并且带有virtual functions,也就不再展现bitwise semantics了。下面的初始化与其对应的类结构:
Bear yogi; Bear winnie = yogi;

yogi和winnie的关系
当一个base class object以其derived class的object内容做初始化操作时,其vptr复制操作也必须保证安全:
Bear yogi; ZooAnimal franny = yogi; //注意,这里会发生切割行为 franny.draw(); //这里调用的是ZooAnimal的draw成员函数,如果是ZooAnimal &franny = yogi,则调用Bear::draw();

franny和yogi的关系
合成出来的ZooAnimal copy constructor会明确设定object的vptr指向ZooAnimal class的virtual table,而不是直接从右手边的class object中将其现值拷贝过来。
copy constructor的应用,迫使编译器多多少少对你的程序代码做部分转化。尤其是当一个函数以传值(by value)的方式传回一个class object,而该class object有一个copy constructor(不论是明确定义出来的,或是合成的)时。这将导致深奥的程序转化——不论在函数的定义或应用上。举个例子:
X bar()
{
X xx;
// ...处理 xx
return xx;
}
编译器会把其中的xx以__result取代:
void bar( X &__result )
{
// default constructor被调用
// C++伪码
__result.X::X();
// ...处理xx
return; //注意是直接return
}
这样的编译器优化操作,有时候被称为Named Return Value(NRV)优化,NRV优化如何被视为是标准C++编译器的一个义不容辞的优化操作。对于这样的NRV优化操作,需要一个explicit copy constructor定义来激活。但是并不是所有的代码都可以进行这样的优化,复杂的函数会使NRV优化难以实施。
三、Member Initialization List
对于没有使用initialization list的情况:
class Word {
String _name;
int _cnt;
public:
Word() {
_name = 0;
_cnt = 0;
}
};
上面的代码是没有错误的,但是实际上,Word Constructor会先产生一个暂时性的String object,然后将它初始化,再以一个assignment运算符将暂时性object指定给_name,然后再摧毁那个暂时性object,下面是编译器可能的内部扩张结果:
// C++伪码
Word::Word( /* this pointer goes here */ )
{
// 调用String的default constructor
_name.String::String()
// 产生暂时性对象
String temp = String(0);
// "memberwise"地拷贝 _name
_name.String::operator=( temp );
// 摧毁暂时性对象
temp.String::~String();
_cnt = 0;
}
当然,更有效率的初始化方法是这样的:
Word::Word : _name(0)
{
_cnt = 0;
}
这样使用initialization list初始化大致会被扩展成下面这样:
// C++伪码
Word::Word( /* this pionter goes here */ )
{
// 调用String( int ) constructor
_name.String::String( 0 );
_cnt = 0;
}
对于initialization list的语法,其实际上并不是一组函数调用,而是对list对象的copy constructor的调用或者是直接赋值(如int)。编译器会一一操作initialization list,以适当次序在constructor之内安插初始化操作,并且在任何explicit user code之前,而list中的项目次序是由class中members声明次序所决定的,不是由initialization list中排列次序决定的。