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。

时间: 2024-08-10 03:39:05

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

《深度探索C++对象模型》第二章 | 构造函数语意学

默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一个成员对象,该成员对象拥有默认构造函数,那么这个类的隐式默认构造函数就是非平凡的,编译器需要为该类合成默认构造函数.为了避免合成出多个默认构造函数,编译器会把合成的默认构造函数.拷贝构造函数.析构函数和赋值拷贝操作符都以内联的方式完成.一个内联含有具有静态链接,不会被文件以外者看到.如果函数不适合做成内联,就

【深度探索C++对象模型】第二章 构造函数语意学(上)

第二章 构造函数语意学(The Semantics of Constructors) -- 本书作者:Stanley B.Lippman 一.前言 首先让我们来梳理一个概念: 默认构造函数(Default Constructor) : 是在没有显示提供初始化式时调用的构造函数.它由不带任何参数的构造函数,或是为所有形参提供默认实参的构造函数定义.如果定义的某个类的成员变量没有提供显示的初始化式时,就会调用默认构造函数(Default Contructor). 如果用户的类里面,没有显示的定义任何

构造函数语意学

C++参考手册告诉我们:default constructors -在需要的时候被编译器产生出来.关键字眼是:在需要的时候. 被谁需要? 做什么事? 当编译器需要它的时候(注意是编译器需要,而不是程序的需要),此外被合成出来的constructor只执行编译器所需要的行为(而不会执行程序所需要的行为,这个设计类的程序员负责). C++ standard 规定: 对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被

【C++对象模型】构造函数语意学之二 拷贝构造函数

关于默认拷贝构造函数,有一点和默认构造函数类似,就是编译器只有在[需要的时候]才去合成默认的拷贝构造函数. 在什么时候才是[需要的时候]呢? 也就是类不展现[bitwise copy semantics]时,即不展现[逐位次拷贝]时,才会合成默认拷贝构造函数. 所谓的[逐位次拷贝],也就是简单的赋值,不管类内的数据成员是int还是char*指针,都是简单的赋值,这叫[逐位次拷贝]. 那什么请下不展现[逐位次拷贝]呢? 有四种情况: ①类中有一个类对象成员,而该类对象成员声明了一个默认拷贝构造函数

【C++对象模型】构造函数语意学之一 默认构造函数

默认构造函数,如果程序员没有为类定义构造函数,那么编译器会在[需要的时候]为类合成一个构造函数,而[需要的时候]分为程序员需要的时候和编译器需要的时候,程序员需要的时候应该由程序员来做工作,编译器需要的时候则由编译器来做工作. C++中,全局变量 / 对象的内存会被清零(如果类对象没有程序员定义的构造函数的时候), 而堆heap 或 栈stack上的变量或对象则不会被清零,其内存只取决于上一次这段内存的值. 而编译器什么情况下会为类合成默认构造函数呢?会在以下四种情况合成默认的构造函数: 1.类

深度探索C++对象模型 第二章构造函数语意学

在使用C++时,常常会好奇或者抱怨,编译器为我们做了什么事呢? 为什么构造函数没有为我初始化呢?为什么我还要写默认构造函数呢? 2.1 Default Constructor 的构造操作 如果没有声明默认构造函数,编译器会在需要的时候帮我们产生出来. 为了避免在多个地方被需要导致重复,则编译器将产生的构造函数声明为inline方式. class Foo {public:Foo(), Foo(int) }; class Bar {public: Foo foo;char *str;} Bar ba

C++构造函数语意学——默认构造函数

概述 在 class 中,若程序员没有为该 class object 定义 default constructors,则编译器会根据需要产生一个 implicit default constructor,该 implicit default constructor 被认为是 trivial(无用的).那编译器怎样才能产生一个 nontrivial implicit default constructor?以下四个方面会产生nontrivial implicit default construct

C++构造函数语意学——默认拷贝构造函数

概述 使用 class object 时,在以下三种情况会以一个 object 的内容作为另一个 class object 的初值,即用到拷贝构造函数: 定义一个 class object 并对其进行初始化: class object 作为一个参数传递给函数: class object 作为函数的返回值: 若用户没有显示声明或定义拷贝构造函数,则 C++ 在 必要 时为 class 声明或定义隐式拷贝构造函数,若出现以下的情况时,该拷贝构造函数是 trivial 的: their class h

深度探索C++对象模型之第二章:构造函数语意学之Default constructor的构造操作

C++新手一般由两个常见的误解: 如果任何class没有定义默认构造函数(default constructor),编译器就会合成一个来. 编译器合成的的default constructor会显示的设定“class内每一个data member的默认值” 一.编译器在哪种情况下才会合成默认构造函数: 对于未声明构造函数的类,只有在以下四种情况下编译器才会为它们合成默认构造函数: 类的成员有一个类对象(Member Class Object),且该成员含有默认构造函数(default Const