首页 > 代码库 > C++构造函数语意学--编译器在哪些情况合成default constructot
C++构造函数语意学--编译器在哪些情况合成default constructot
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。
C++构造函数语意学--编译器在哪些情况合成default constructot