构造函数语意学

C++参考手册告诉我们:default constructors
…在需要的时候被编译器产生出来。关键字眼是:在需要的时候。
被谁需要? 做什么事?

当编译器需要它的时候(注意是编译器需要,而不是程序的需要),此外被合成出来的constructor只执行编译器所需要的行为(而不会执行程序所需要的行为,这个设计类的程序员负责)。

C++ standard 规定:

对于class X,如果没有任何user-declared
constructor,那么会有一个default
constructor被暗中(implicitly)声明出来… 一个被暗中声明出来的default constructor将是一个trival constructor.

一个nontrivial default
constructor在ARM(C++ Annotated Reference Manual)的术语中就是编译器所需要的那种,必要的话会被编译器合成出来。

有四种情况需要nontrivial default constructor


1 “带有default constructor”的member class object

如果一个class没有任何构造函数,但它内含一个member class
object,而后者有default
constructor,那么这个class 的implicit defaut
constructor就是“nontrival”,编译器需要为此class合成一个默认构造函数,不过这个合成操作只在构造函数真正需要调用时才会合成。

注意:

1>合成的默认构造函数并不产生任何代码来初始化非类对象成员,这些成员的初始化是程序的责任。即:合成的默认构造函数只满足编译器的需要。

2>如果class A内含一个或一个以上的member class object,那么class A的每一个构造函数必须调用member
class的默认构造函数。如果没有显示调用,编译器会扩展已存在的构造函数,在其中插入一些码,使得user
code执行之前,先调用必要的默认构造函数。

3>C++语言要求以“member objects
在class中声明次序”来调用各个构造函数。这一个有编译器完成,并且在显示用户代码之前完成。

2 “带有一个default constructor function”的base class

如果一个没有任何constructors的class派生自一个“带有一个default constructor
function”的base class,那么这个派生类的默认构造函数会被视为nontrival,并因此被合成出来,它将调用上一层的default
constructor(根据他们声明的次序)。

如果类设计已经定义了多个constructors,编译器会扩张现有的每一个constructors,将“用以调用所有必要之默认构造函数”的程序代码加进去。如果同时亦存在“带有default constructors”的member
class objects,那些default constructor也会被调用-在所有base
class constructors被调用之后,但user
code执行之前。

3 "带有一个virtual function" 的class?

为了让虚函数展现的动态绑定机制发挥出来,编译器必须为每一个基类(或其派生类)对象的vptr设定初始值,放置适当的virtual
table的地址。对于class所定义的每一个constructors,编译器会安插一些代码来做这样的事情。对于那些未声明任何constructors的classes,编译器会为他们合成一个default
constructor,以便正确初始化每一个class object的vptr。

4 “带有一个virtual base class”的class

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

cfront编译器的做法是靠“在derived class
object的每一个virtual base classes中安插一个指针”完成。所有“经由reference 或
pointer来存取一个virtual base class”的操作都可以通过相关指针完成。

这个指针(或编译器所做出的某个什么东西)是在class
object构建期被完成初始化的。对于class 所定义的每一个constructors,编译器会安插那些“允许每一个virtual base
class的执行期存取操作”的码。如果class没有声明任何constructors,编译器为它合成一个default
constructor.

总结

以上四种情况,会导致“编译器必须为未声明constructor之class合成一个default
constructor”。C++标准把那些合成物称为implicit
nontrivial default
constructor。被合成出来的constructor只能满足编译器(而非程序)的需求。至于没有存在以上四种情况而没有声明任何constructor的class,我们说他们拥有的是implicit
trivial default constructor,他们实际上并没有被合成出来。

在合成出来的default
constructor中,只有base class
subjects和member class object会被初始化,所有其他的nonstatic

data member,如整数、整数指针、整数数组等等都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则并非必要。

copy constructor的建构操作

有三种情况,会以一个object的内容作为另一个class
object的初值。1> 对一个对象做明确的初始化操作(X xx=x;)2>当object被当作参数交给某个函数 3>
当函数传回一个class object时。

假设class设计者明确定义了一个copy
constructor,那么大多数情况下,当一个class
object以另一个同类实体作为初值时,copy
constructor会被调用。

如果class没有提供一个explicit copy
constructor有如何?

1 当class
object以“相同class的另一个object”作为初值时,其内部是以所谓的default
memberwise initialization手法完成的,也就是把每一个内建的或派生的data
member(例如一个指针或一个数组)的值,从某个object拷贝一份到另一个object身上。

不过它并不会拷贝其中的member
class object,而是以递归的方式施行memberwise initialization。

2 这样的操作实际上如何完成?ARM告诉我们:

1> 从概念上而言,对一个class
X,这个操作是被一个copy constructor实现出来...。

2> 一个良好的编译器可以为大部分class
objects产生bitwise copies,因为他们有bitwise copy semantics...。也就是说,"如果一个class未定义出copy
constructor,编译器就自动为它产生出一个”这句话不对。而是应该想ARM所说:

3> default
constructors和copy constructors在必要的时候才由编译器产生出来。“必要”意指class不展现bitwise
copy semantics时。

3 C++标准仍保留了ARM的意义,表述如下:

就像default
constructor一样,C++标准上说,如果class没有声明一个copy constructor,就会隐含的声明(implicitly
declared)或隐含的定义(implicitly defined)出来。和以前一样,C++标准把copy
constructor区别为trivial和nontrivial两种。只有nontrivial的实体才会被合成与程序之中。

决定一个copy
constructor是否为trivial的标准在于class是否展现出所谓的“bitwise copy
semantics"。

在一个类未展现出bitwise copy
semantics,而且没有定义一个显示copy constructor,编译器必须合成出一个copy
constructor以便实施一些bitwise
copy不能完成的任务。

注意:

被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制。

4 什么时候一个class不展现出bitwise copy
semantics呢?有四种情况:

1> 当class内含一个member
object
,而后者的class声明有一个copy
constructor时(不论是被class设计者明确地声明,或是是被编译器合成)。

2> 当class继承自一个bass
class而后者存在有一个copy constructor时
(再次强调,不论是被明确声明或被合成而得)。

3>
当class声明了一个或多个virtual functions时。这种情况编译器会为每一个class
object安插一个vptr指针,这样该类就不展现bitwise semantics了。现在,编译器要合成一个copy
constructor,以求将vptr适当地初始化。

注意:


当一个类对象以另一个相同的类对象作为初值时,都可以直接靠"bitwise copy semantics"完成。

② 当一个base class
object以其derived class的object内容初始化操作时,其vptr复制操作必须保证安全。合成出来的基类copy
constructor会明确设定object的vptr指向基类的virtual table,而不是直接从右手边的派生类中将其vptr现值拷贝过去。

4>
当class派生自一个继承串联,其中有一个或多个virtual base classes时

virtual base class
的存在需要特别处理。一个class object如果以另一个object作为初值,而后者有一个virtual class
subobject,那么会使"bitwise copy semantics"失效。

每一个编译器对于虚拟继承的支持承诺,都表示必须让“derived class object中的virtual base class
subobject位置”在执行期备妥。维护“位置的完整性”是编译器的责任。bitwise copy
semantics可能会破坏这个位置,所以编译器必须在它自己合成出来的copy
constructor中做出仲裁(一部分工作是安插一些码设定virtual base class
pointer/offset的初值或简单地确定它没有被摸消)。

5 总结

在上面四种情况下,class不再保持“bitwise
copy semantics”,而且copy constructor如果未声明的话,会视为nontrivial。在这四种情况下,如果缺乏一个已声明的copy
constructor,编译器为了正确处理“以一个class
object作为另一个class object的初值”,必须合成出一个copy constructor。

6 copy constructor的应用

copy
constructor迫使编译器多多少少对你的程序代码做部分转化,尤其是当一个函数以传值(by value)方式传回一个class
object,而该class有一个copy constructor(不论是明确定义出来的,或是合成的)时,这将导致深奥的程序转化-不论在函数的定义和使用上。

构造函数语意学,布布扣,bubuko.com

时间: 2024-12-24 19:36:24

构造函数语意学的相关文章

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

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

【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++对象模型》第二章 | 构造函数语意学

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

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

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

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

C++新手常有的误解: C++新手一般有两个常见的误解: 1.任何class如果没有定义default constructor,就会被合成出一个来. 2.编译器合成出来的default constructor会显示设定"class 内每一个data member的默认值" 有四种情况,会导致"编译器必须为未声明的constructor之classes合成一个defaultconstructor",c++ standan 把那些合成物成为implicit nontri

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

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

C++对象模型-构造函数语意学

由于编译器会尽可能的为所有的警告和错误做出解释,但也因此导致了部分情况下的过度解析. 书中给的例子是编译器过度解析,使用了类型转换函数却隐藏了真正的错误. cin << intval; int temp = cin.operator int(); temp << intval; 分析一下: 程序员的目的是实现读取输入,但是将 >> 写成了 <<, istream并没有重载 << 运算符,结果编译器按照 << 左移位来解析 要想实现左移