C++构造函数语意学--编译器在哪些情况合成default constructot

来源:互联网 发布:mmd虎视眈眈镜头数据 编辑:程序博客网 时间:2024/06/05 14:43

C++新手常有的误解:

C++新手一般有两个常见的误解:

1.任何class如果没有定义default constructor,就会被合成出一个来。

2.编译器合成出来的default constructor会显示设定“class 内每一个data member的默认值”

       有四种情况,会导致“编译器必须为未声明的constructor之classes合成一个defaultconstructor”,c++ standan 把那些合成物成为implicit nontrivial default constructors,被合成出来的constructor智能满足编译器(而非程序)的需要,它之所以能够完成任务,是借着“调用member object或base class的default constructor”或是“为每一个object初始化其virtual function机制或virtual base class机制”而完成。至于没有存在那四种情况而又没有声明任何constructor的classes。我们说他们拥有的是implict trivial default constructor(隐式无用的缺省构造函数),它们实际上并不会被合成出来。

      在合成的defaultconstructor中,只有base class subobjects 和member class object会被初始化。所有其他的nonstatic data member,如整数,整数指针,整数数组,等等都不会被初始化,这些初始化操作队程序而言或许需要,但对编译器则并非必要,如果程序需要一个“把某指针设为0”的default constructor,那么提供他的人应该是程序员.

      

         对于一个类,如果没有任何构造函数的声明,那么会有一个default constructor被隐式声明出来。一个隐式声明出来的default constructor是trivial  constructor(无用构造函数)。但编译器需要时,会合成一个nontrivialdefault constructor。有四种情况会合成nontrivial  default 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 class objects,那些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而存取的X::i”的实际偏移位置,因为pa的真正类型可以改变。编译器必须改变执行存取操作的那些代码,是X::i可以延迟至执行期才决定下来。原先cfront的做法是靠“在derived class object的每一个virtual baseclasses中安插一个指针”完成。所有“经由reference或pointer来存取一个virtualbase class”的操纵都可以通过相关指针完成。在我的例子中,foo()可以被改写如下,以符合这样的策略:

//可能的编译器转变操作

void foo( const A* pa ){ pa->_vbcX->I= 1024; }

其中,_vbcX表示编译器所产生的指针,指向virtualbase class X。

_vbcX(或编译器所作出的某个东西)是在class object构造期间被完成的。对于class所定义的每一个constructor,编译器会安插那些“允许每一个virtual base class的执行期存取操作”的代码。如果class没有声明任何constructors,编译器必须为它合成一个default  constructor。


1 1