2.1 Default Constructor的建构操作
以下四种情况,编译器必须为class合成default Constructor!
2.1.2 带有Default Constructor的Member Class Object
如果一个class没有任何Constructor,但是它内含一个member object,且member object有default Constructor,那么这个class的implicit default Constructor就是non-trivial,编译器需要为此class合成一个 default Constructor,不过这个合成操作只有在Constructor真正需要被调用时才会发生。
class Foo
{
public:
Foo(){}
Foo(int) {}
}
class Bar
{
public:
Foo foo;
char *str;
}
void foo_bar()
{
Bar bar;
//Bar::foo必须在此处初始化
}
如果有多个member object都要求有Constructor初始化操作,那么会按照member object在class内的声明次序来调用每个的Constructor,且这些操作被安插在explicit user code(也就是成员变量)之前
class A
{public: A();}
class B
{public: B();}
class C
{public: C();}
class D
{
public:
//D没有定义default constructor
A a;
B b;
C c;
private:
//explicit user code
int x;
}
如上述代码所示,D没有default Constructor,那么就会有一个nontrivial Constructor被合成出来,依次调用A B C的default Constructor,即:
D::D()
{
a.A::A();
b.B::B();
c.C::C();
x = 0;
}
2.1.3 带有Default Constructor的Base Class
如果基类base class带有default Constructor,派生类没有任何Constructor,那么这个派生来的default Constructor会被视为non-trivial,并因此需要被创建出来。
2.1.4 带有一个Virtual Function的Class
当一个class中有虚函数时,编译器编译时会出现的操作有:
1.一个vbtl会被编译器产生出来,内放class的virtual function地址
2.每一个class object中,一个额外的pointer member(即vptr)会被编译器合成出来,内含相关的class vtbl的地址
3.当调用虚函数时,会出现虚拟引发操作的改写,以使用vptr、vbtl的条目,即(*x.vptr[1])(&x)
为了让以上操作生效,编译器必须为每一个class object的vptr设定初值,放置适当的vtbl的地址。因此对于没有default Constructor的类,必须合成一个Constructor,以便能够初始化每一个vptr。
2.1.5 带有一个Virtual Base Class的Class
编译器会在派生来对象的每一个虚基类中插入一个指针,所有经由指针或引用来存取虚基类的操作都可以通过该指针来完成。
被插入的这个指针会在Constructor期间完成,因此如果该class没有default Constructor,编译器会为它合成一个。
2.2 Copy Constructor的建构操作
复制构造函数可能用到的情况:
1.新建某对象时需要用另一个object来做初值拷贝
2.函数参数拷贝时
3.函数返回一个值时
2.2.1 Default Memberwise Initiailization
当class没有提供explicit copy Constructor时,class object以另一个object拷贝时内部运用的是default memberwise initiatiltion,且当类成员中有其他member class object时,拷贝这个object时是以递归的方式施行memberwise initialzation。
2.2.2 Bitwise Copy Semantics(位逐次拷贝)
Word noun("book");
void foo()
{
Word verb = noun;
}
上述代码中,如果Word定义了一个copy Constructor,verb的初始化操作会调用它,但是如果未定义复制构造函数,那么编译器是否会合成一个default copy Constructor取决于class是否展现Bitwise copy semantics特性。
class Word
{
public:
Word(const char*);
~Word();
private:
int cnt;
char* str;
}
如果使用的是上面的class Word,那么就不会合成一个default copy Constructor,因为上述展现的是Bitwise copy semantics特性,无需调用一个复制构造函数。
class Word
{
Word(const String&);
~Word();
private:
int cnt;
String str;
}
class String
{
public:
String(const char*);
String(const String&);
~String();
}
如果使用的是上面的class String,那么必须合成一个copy Constructor以便调用member class String object的copy Constructor。调用过程伪代码如下所示:
Word::Word(const Word &wd)
{
str.String::String(wd.str);
cnt = wd.cnt;
}
2.2.3 不要Bitwise Copy Semantics!
class不展现Bitwise copy semantics特性的四种情况:
1.当class内含一个member object而后者的class声明有一个copy Constructor时(不论是被class设计者明确的声明,像前面的class String,或是被编译器合成,像class Word)
2.当class继承自一个base class而后者存在有一个copy Constructor时
3.当class声明一个或多个virtual functions时
4.当class派生自一个继承串链,其中有一个或多个virtual base classes时
2.2.4 重新设定Virtual Table的指针
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
private:
//ZooAnimal的animate(),draw()所需要的数据
}
class Bear:public ZooAnimal
{
public:
Bear();
virtual void animate();
virtual void draw();
virtual void dance();
private:
//Bear的animate(),draw(),dance()所需要的数据
}
Bear yogi;
Bear winnie = yogi;
yogi会被Bear的构造函数初始化,而在构造过程中,yogi的vptr被设定指向Bear class的virtual table,因此把yogi的vptr值拷贝给winnie的vptr是安全的。两者的具体关系见下图:
当base class object以其Derived class的object内容做初始化时,其vptr复制操作也必须保证安全,例如
ZooAnimal franny = yogi;
franny的vptr不可以被设定指向Bear class的virtual table,否则下面代码中draw被调用时而franny被传进去,就会出现问题
void draw(const ZooAnimal& zoey)
{zoey.draw();}
void foo()
{
//franny的vptr指向ZooAnimal的virtual table
ZooAnimal franny = yogi;
draw(yogi); //调用Bear::draw()
draw(franny); //调用ZooAnimal::draw()
}
2.2.5 处理virtual base class subobject
class Raccoon: public virtual ZooAnimal
{
public:
Raccoon();
Raccoon(int val);
private:
//所有必要的数据
}
class RedPanda :public Raccoon
{
public:
RedPanda();
RedPanda(int val);
private:
//所有必要的数据
}
RedPanda little_red;
Raccoon little_critter = little_red;
上述代码中,为了完成正确的little_critter初值设定,编译器必须合成一个copy Constructor来设定virtual base class pointer/offset的初值。
2.3 程序转化语义学
2.3.1 明确的初始化操作
void foo_bar()
{
X x1(x0);
X x2 = x0;
X x3 = X(x0);
}
必要的程序转化有两个阶段:
1.重写每一个定义,其中的初始化操作会被剥除;
2.class的copy Constructor调用操作会被安插进去;
2.3.2 参数的初始化
这里指的是函数参数的初始化,即
void foo(X x0);
X xx;
foo(xx);
x0为形参,xx为实参,x0以memberwise的方式将xx当作初值,此时编译器会导入一个暂时性的object,并调用copy Constructor将它初始化,然后将此暂时性object交给函数,具体过程如下面为伪代码所示:
X tmp;
tmp.X::X(xx);
foo(tmp);
2.3.3 返回值的初始化
X bar()
{
X xx;
....
return xx;
}
函数返回值是如何从局部对象xx中拷贝过来的?过程如下:
1.首先加上一个额外参数,类型是class object的一个reference,该参数将用来放置被“拷贝建构”而得的返回值。
2.在return指令之前安插一个copy Constructor调用操作,以便将欲传回之object的内容当做上述新增参数的初值。
具体过程的伪代码如下:
void bar(X& _result)
{
X xx;
//编译器所产生的default constructor调用操作
xx.X::X();
.....
//编译器所产生的copy constructor调用操作
_result.X::XX(xx);
return;
}
那么当用到bar()函数时,编译器都会进行转换工作,例如:
X xx = bar();
//转换
X xx;
bar(xx);
2.3.4 在使用者层面做优化
这种优化就是定义一个“计算用”的Constructor。
X bar(const T &y,const T &z)
{
X xx;
return xx;
}
X bar(const T &y,const T &z)
{
return X(y,z);
}
当设定一个class X的Constructor时,就无需像之前提到的那样调用复制构造函数来得出_result,具体过程伪代码:
void bar(X &_result)
{
_result.X::X(y,z);
return;
}
2.3.5 在编译器层面做优化
X bar()
{
X xx;
....
return xx;
}
void bar(X &_result)
{
//default constructor被调用
_result.X::X();
//...直接处理_result
return;
}
如上述代码所示,编译器层面的优化指的是以_result取代xx,并且之后的操作都是对_result来操作,这种优化被称为NRV(Named Return Value)
2.3.6 Copy Constructor:要还是不要?
根据具体情况分析即可!
2.4成员们的初始化队伍
必须使用成员初始化列表(member initialization list)的四种情况:
1.当初始化一个reference member时
2.当初始化一个const member时
3.当调用一个base class的Constructor时,而它拥有一组参数
4.当调用一个member class的Constructor时,而它拥有一组参数
class Word
{
public:
Word()
{_name = 0; _cnt = 0;}
public:
String _name;
int _cnt;
}
上述代码中,class的构造函数在调用时,会产生一个暂时性的String object并初始化,再以assignment运算符将暂时性object赋予_name,然后再摧毁这个tmp,具体过程伪代码如下:
Word::Word(//参数内含一个this pointer)
{
//调用String的default Constructor
_name.String::String();
//产生暂时性对象
String temp = String(0);
//memberwise拷贝
_name.String::operator=(temp);
//摧毁暂时性对象
temp.String::~String();
_cnt = 0;
}
如果使用的是初始化成员列表,那么将更有效率:
Word::Word:_name(0)
{
_cnt = 0;
}
//伪代码
Word::Word(//参数内含一个this pointer)
{
//调用String(int)constructor
_name.String::String(0);
_cnt = 0;
}
PS:member initialization list的次序是由class中的members声明次序决定的,且被安插在explicit user code之前
PS1:初始化成员列表中能否调用一个member function来初始化成员?
X::X(int val):i(xfoo(val)),j(val)
{}
答案是可以的,但是要注意的是务必使用存在于Constructor体内的一个member,而不要使用存在于member initialization list中的member,来为另一个member设定初值。扩充过程伪代码:
X::X(/* this pointer, */ int val)
{
i = this->xfoo(val);
j = val;
}
PS2:如果一个派生类成员函数被调用,其返回值被当做base class Constructor的一个参数,例如
class FooBar:public X //base class X
{
int _fval;
public:
int fval() {return _fval;} //derived class member
FooBar(int val):_fval(val),X(fval()) //fval()作为base class Constructor的参数
};
这样操作是错误,扩张过程伪代码如下:
FooBar::FooBar(//this pointer)
{
X::X(this, this->fval());
_fval = val;
}
上述代码中,编译器会调整初始化成员列表的顺序来适配成员变量的声明次序,因此上述代码会出现错误。