深度探索C++对象模型 2构造函数语意学(一)

2014-11-24 07:38:40 · 作者: · 浏览: 2

第二章 构造函数语意学

本章大量出现的英语术语:

trivial: 没有用的

nontrivial: 有用的

memberwise: 对每一个member施以…

bitwise: 对每一个bit施以…

named return value optimization (NRV)

2.1Default Constructor的构造操作

对于一个类,如果没有任何构造函数的声明,那么会有一个default constructor被隐式声明出来。一个隐式声明出来的default constructor是trivialconstructor。但编译器需要时,会合成一个nontrivialdefault constructor。有四种情况会合成nontrivialdefault constructor。

1. 带有defaultconstructor的member class object

如果一个class没有任何constructor,但它内含一个member object,而后者有defaultconstructor,那么这个class的implicit default constructor就是nontrivial,编译器需要为该class合成一个default constructor。不过合成操作只有在constructor真正需要被调用时才会发生。

在各个C++不用模块中如何避免合成出多个default constructor:解决办法是把合成的defaultconstructor、copy constructor、assignment copy operator都以inline方式完成。一个inline函数有静态链接,不会被文件外者看到。如果函数太复杂,不会适合做inline,就会合成一个explicitnon-inline static实例。例如:

class Foo {public: Foo(), Foo( int ) … };

class Bar {public: Foo foo; char *str;};

Bar bar; //Bar::foo必须在此处初始化,Bar::foo是一个memberobject,并且有default //constructor。故编译器合成defaultconstructor。

此处,将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。故合成

的default constructor看起来像这样:

inline Bar::Bar(){

foo.Foo::Foo();

}

但如果程序员提供了一个default constructor,如下:

Bar::Bar(){ str= 0; }

由于已经存在一个default constructor,所以编译器没法合成第二个。编译器的行动是:如果类内含有一个或一个以上的member class objects,那么类的每一个constructor必须调用每一个member classes的defaultconstructor;编译器会扩展已存在的constructors,在其中安插一些代码,是的user code被执行之前,先调用必要的defaultconstructor。

则上面扩张后可能像这样:

Bar::Bar(){

foo.Foo::Foo();

str = 0;

}

如果有多个class member objects都要求constructor初始化操作,C++语言要求member objects在class中声明的顺序来调用各个constructors。这一点由编译器完成,它为每一个constructor安插代码程序,以member声明顺序调用每一个member所关联的default constructors。这些代码将被安插在explicit user code之前。

2.带有defaultconstructor的base class

如果一个没有任何constructor的class派生自一个带有default constructor的base class,那么这个default constructor会被视为nontrivial,并因此需要被合成出来。它将调用上一层base class的defaultconstructor(根据它们的声明顺序)。

如果有多个constructors,但其中都没有defaultconstructor,编译器会扩张先有的每一个constructors,将用以调用所有必要的default constructors的程序代码加进去。如果同时又存在着带有default constructor的member classobjects,那些default constructors也会在所有base class constructors都被调用之后被调用。

3.带有一个virtualfunction 的class(声明或继承)

class Widget{

public:

virtualvoid flip () = 0;

};

void flip( const Widget & widget ){widget.flip();}

//假设Bell和Whistle是Widget的派生类

void foo(){

Bellb;

Whistle w;

flip( b );

flip( w );

};

下面两个扩张行动会在编译期间发生:

1) 一个virtual function table(vtbl)会在编译器产生出来,内放class的virtual function地址。

2)在每一个class object中,一个额外的pointermember会被编译器合成出来,内含相关的classvtbl地址。

此外,widget.flip()的虚拟调用操作会被重写,以使用widget的vptr和vtbl中的flip()条目。

(*widget.vptr[ 1 ] )( &widget );

为了让这个机制(虚拟机制)发挥功效,编译器必须为每一个Widget object的vptr设定初值,放置适当的virtual table地址。对于class 所定义的每一个constructor,编译器会安插一些代码来做这样的事情(见5.2节)。对于那些未声明任何constructors的classes,编译器会为它们合成一个defaultconstructor,以便正确初始化每一个class object的vptr。

4.带有一个virtual baseclass的class

Virtual base class的实现方法在不同的编译器之间有极大的差异。然而,每一种实现法的共同特点在于使virtual base class在其每一个derivedclass object中的位置,能够于执行期准备妥当。例如:

class X { public: int i; };

class A:public virtual X { public: int j;};

class B:public virtual X { public: doubled;};

class C:public A, public B { public: intK;};

void foo( const A * pa){ pa->i = 1024; } //无法再编译时期决定出pa->X::i的位置

main(){

foo(new A );

foo(new C );

}

编译器无法固定foo()之中“经由pa而存