关于默认构造函数

一般不少C++程序员存在两种误解:c++

  • 没有定义默认构造函数的类都会被编译器生成一个默认构造函数。
  • 编译器生成的默认构造函数会明确初始化类中每个数据成员。

C++标准规定:若是类的设计者并未为类定义任何构造函数,那么会有一个默认 构造函数被暗中生成,而这个暗中生成的默认构造函数一般是不作什么事的(无 用的),trivial下面四种状况除外。程序员

换句话说,有如下四种状况编译器必须为未声明构造函数的类生成一个会作点事 的默认构造函数。(nontrivial constructor)咱们会看到这些默认构造函数仅“忠于编译器”,而可能不会按 照程序员的意愿效命。ide

1.包含有带默认构造函数的对象成员的类(带有default constructor的member class object)

若是一个class没有任何constructor,但他含有一个member object,然后者有defautl constructor,那么这个class的implicit函数

defautl constructor就是“nontrivial”,编译器须要为此class合成出一个default constructor,不过这个合成操做只有在constructor真正须要被调用时发生。设计

class Foo {public: Foo(); Foo(int);...};指针

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

void foo_bar(){对象

   Bar bar; //Bar::foo应在此处被初始化继承

if(str){...}ip

}

被合成的Bar default ctor内含必要的代码,可以调用class Foo的default ctor来处理member object Bar::foo,但并不处理Bar::str。即被合成的default ctor只是为了知足编译器的须要(编译器须要有个地方来初始化Bar::Foo,由于它有本身的default ctor),而不是程序的须要(初始化Bar::str是程序员的动做)。

若是程序员在ctor只显式初始化了Bar::str,但对编译器而言,它还须要初始化member object foo,因为无参构造函数已经被明肯定义出来,编译器没办法合成第二个。那么这时候编译器会怎么作呢?

 

编译器的行动是“若是class A内含一个或以上的member class objects,那么classA的每个constructor必须调用每个member classs的defautl constructor,编译器会扩张已存在的constructor。在其中安插一些代码。使得user code在被执行以前,先调用必要的default constructor,)

若是有多个class member object都要求constructor的初始化操做,将如何呢?c++要求以“member objects在class中的声明次序”来调用各个constructor。这一点有编译器完成。

例如

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

会被编译器扩展为

Bar::Bar(){ 

     foo.Foo::Foo(); //附加上的编译器代码

            str=0;//显式的程序员代码

}

2.带有defautl constructor“的base class。

若是一个没有定义任何构造函数的类派生自带有默认构造函数的基类,那么编译器为它定义的默认构造函数,将按照声明顺序为之依次调用其基类的默认构造函 数。若该类没有定义默认构造函数而定义了多个其余构造函数,那么编译器扩充它的全部构造函数——加入必要的基类默认构造函数。另外,编译器会将基类的默认构造函数代码加在对象成员的默认构造函数代码以前。(注:对于基类有默认构造函数的,而在子类中无显式构造函数时,编译器对于基类部分的数据以及数据成员不会视而不见的,并且是先基类后对象成员,对于有多个基数的,按照它们在继承列表中的顺序进行调用;固然即便你定义了其余类型的构造函数,编译器也会在后面帮你完成那些你没有显式完成的工做,它会扩充全部你编写的构造函数,固然只是在你没有显式调用基类的哪一个构造函数时)。

3.带有一个virtual function的class

另有2中状况,也须要合成出defautl constructor。

1.     这个类本身声明(或者继承)了Virtual Function。

2.     这个类继承自一个继承串链,其中有一个或多个virtual base class。

无论哪种状况,因为缺少有user声明的constructor,编译器会详细记录合成一个defautl constructor的必要信息,如下面的程序片断为例;

class Widget{

  public:

       virtual void flip()=0;

     //...

};

void flip(const Wideget& widget) { widget.fiip();}

//假设Bell和whistle都派生子widget

void foo()

{

  Bell b;

Whistle w;

flip(b);

flip(w);

}

这种状况下,编译时,要作以下工做:

1.编译器须要生成一个virtual function table(vtbl)并填充。

2.在class object中,一个额外的pointer member(就是vptr,指向vtbl)会被编译器合成出来。此外虚拟调用会被替换(w.vf() => w.vprt[1])。

为了支持这种功能,编译器必须为每一个w对象设置它的vptr(这是成员变量,此时须要指向合适的vtbl),所以编译器须要在default ctor中安插一些代码来完成这种工做。

4.带有一个virtaul Base class的class

virtual base class的实现法在不一样编译器之间有极大的差别,然而,每一种实现法的共同点在于必须使virtaul base classs在其每个derived class object中的位置,可以于执行期准备稳当,例以下面这段程序代码中:

考虑这样的代码:

classX { public: inti; };

classA : publicvirtualX   { public: in tj; };

classB : publicvirtualX   { public: double d; };

classC : publicA, publicB { public: int k; };

//没法在编译期间解析出 pa->X::i 的位置(给一个pa没法肯定i的地址)。

void foo( constA* pa ) { pa->i = 1024; }

main() {

   foo( new A );

   foo( new C );

   // ...

}

因为pa的真正类型不肯定,因此某些编译器会记录一个指针例(如 __vbcX)来记录X,而后经过这个指针来定位pa指向的i。上述

void foo( constA* pa ) { pa->i = 1024; }

变成了:

void foo( constA* pa ) { pa-> __vbcX ->i = 1024; }

所以,__vbcX这个指针(执行virtual base class)须要在object构造期间设置好。因而编译器须要一个default ctor来完成这个工做。

相关文章
相关标签/搜索