1、Default Constructor

  当编译器需要的时候,default constructor会被合成出来,只执行编译器所需要的任务(将members适当初始化)。

1.1  带有 Default Constructor 的Member Class Object

  编译器的出来是:如果一个class A 内含一个或者一个以上 member class objects ,那么class A 的每一个 constructor 必须调用每一个member classes 的default constructor 。编译器会扩张已存在的constructors,在其中安插一些代码,使得 user code在被执行之前,先调用(调用顺序一member objects在class 的声明次序一致)必要的 default constructors。(例如:一个类中包含了两个成员,一个成员是一个对象,而另一个成员是built-in的变量,那么编译器会调用这个member对象的默认构造器,built-in变量就会置之不理。)

  注意:这样的编译器默认合成构造器,是隐式的,如果类本身存在构造器,那么编译器会在程序需要的(可以理解为user code )代码之前,安插必要的default constructor。

  举个例子,假设我们有以下三个classes:

class Dopey {public: Dopey();...};
class Sneezy {public: Sneezy(int); Sneezy();...};
class Bashful {public: Bashful();...};

  以及一个class Snow_White:

class Snow_White
{
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
private:
int mumble;
};

  如果Snow_White没有定义default constructor,就会有一个nontrivial constructor被合成出来,依序调用Dopey、Sneezy、Bashful的default constructors。然而如果Snow_White定义了下面这样的default constructor:

//程序员所写的default constructor
Snow_White::Snow_White(): sneezy()
{
mumble = ;
}

  它将会被扩张为:

//编译器扩张后的default constructor
Snow_White::Snow_White():sneezy()
{
//插入member class object
//调用其constructor
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy();
bashful.Bashful::Bashful(); //expilict user code
mumble = ;
}

1.2 带有 Default Constructor 的 Base class

  如果没有定义构造函数,编译器会产生一个nontrivial constructor,先调用相应的base class's constructor,如果用户自己定义了constructor,那么编译器将会安插调用所必要的default constructors的程序代码到用户自己定义的constructor中。(通俗地讲:用户已经定义了构造函数,但是没有定义默认构造函数,那么编译器会把默认的东西加在进去,但不会重新写一个default constructor出来)。

1.3 带有一个 Virtual Function 的Class

  下面两种情况同样需要合成default constructor:

  • class 声明(或继承)一个 virtual function。
  • class派生自一个继承串链,其中一个或者更多的 virtual base class。

  扩展(constructor)操作会在编译期间发生:

  • 一个virtual function table 会被编译器产生出来,内放class 的virtual functions 的地址。
  • 在每一个 class object 中,一个额外的pointer member(vptr)会被编译器合成出来,内含相关的class vtbl的地址。

1.4 带有一个 Virtual Base Class 的class

  Virtual base class的实现法在不同编译器之间有很大差异,然而,每一个实现的共同点在于必须使 virtual base class 在其每一个 derived class object中的位置,能够在执行期准备妥当。对于class所定义的每一个constructor 编译器都会安插那些“允许每一个virtual base class 的执行期存取操作”的码。

1.5 总结

  以上四种情况,会导致“编译器必须为未声明constructor 的class 合成一个default constructor ”,这只是编译器(而非程序)的需要。它之所以能够完成任务,是借着“调用member object 或base class的default constructor ”或是“为每一个object初始化其 virtual function 机制或virtual base class 机制”完成。至于没有存在这四种情况而又没有生命constructor的class 实际上是不会被合成出来的。

  在合成的default constructor 中,只有base class subobjects(子对象)和member class objects会被初始化。所有其他的nonstatic data member ,如整数,整数指针,整数数组等是不会被初始化的,这些初始化操作对程序是必须的,但对编译器则并非需要的。

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

  • 任何class 如果没有定义default constructor ,就会被合成出来一个。
  • 编译器合成出来的default constructor 会明确设定 class 内每一个data member的默认值。

2、Copy Constructor

2.1 拷贝情况

  有三种情况,会以一个object的内容作为另一class object的初值。

  1.最明显的当然是对一个object做明确的初始化操作。

  2.当object被当做参数交给某个函数

  3.当函数返回一个class object。

  这三种情况需要有 copy constructor。

2.2 Default Memberwise Initialization

  如果class 没有提供一个 explicit copy constructor(显示拷贝构造函数)时,当class object以“相同的另一个object作为初值是,其内部是以所谓的default memberwise initialization方式完成的。也就是把每一个内建的或派生的 data member(例如一个数组或指针)的值,从某个object拷贝一份到另一个object上,但不拷贝其具体内容。例如只拷贝指针地址,不拷贝一份新的指针指向的对象,这也就是浅拷贝,不过它并不会拷贝其中member class object,而是以递归的方式实行memberwise initialization。

  这种递归的memberwise initialization是如何实现的呢?

  答案就是Bitwise Copy Semantics和default copy constructor。如果class展现了Bitwise Copy Semantics,则使用bitwise copy(bitwise copy semantics编译器生成的伪代码是memcpy函数),否则编译器会生成default copy constructor。

  那什么情况下class不展现Bitwise Copy Semantics呢?

  有四种情况:

  1.当class内含有一个member class object,而这个member class 内有一个默认的copy 构造函数[不论是class设计者明确声明,或者被编译器合成]

  2.当class 继承自 一个base class,而base class 有copy构造函数[不论是class设计者明确声明,或者被编译器合成]

  3.当一个类声明了一个或多个virtual 函数

  4.当class派生自一个继承串链,其中一个或者多个virtual base class

  下面我们来理解这四种情况为什么不能使用bitwise copy,以及编译器生成的copy constructor都干了些什么。

  在前2种情况下,编译器必须将member或者base class的“ copy constructor的调用操作”安插到被合成的copy constructor中。

  第3种情况下,因为class 包含virtual function, 编译时需要做扩张操作:

  1.增加virtual function table,内含有一个有作用的virtual function的地址;

  2.创建一个指向virtual function table的指针,安插在class object内。

  所以,编译器对于每一个新产生的class object的vptr都必须被正确地赋值,否则将跑去执行其他对象的function了,其后果是很严重的。因此,编译器导入一个vptr到class之中时,该class 就不在展现bitwise semantics,必须合成copy Constructor并将vptr适当地初始化。

2.3 处理Virtual Base Class Subobject

  virtual base class的存在需要特别处理。一个class object 如果以另一个 virtual base class subobject那么也会使“bitwise copy semantics”失效。

  每一个编译器对于虚拟继承的支持承诺,都是表示必须让“derived class object 中的virtual base class subobject 位置”在执行期就准备妥当,维护“位置的完整性”是编译器的责任。Bitwise copy semantics 可能会破坏这个位置,所以编译器必须自己合成出copy constructor。

  这也就是说,拷贝构造函数和默认构造器一样,需要的时候会进行构建,而并非程序员不写编译器就帮着构建。

2.4 初始化列表

  下面四种情况必须使用初始化列表来初始化class 的成员:

  1.当初始化一个reference member时;

  2.当初始化一个const member时;

  3.当调用一个base class 的 constructor ,而它拥有一组参数(其实就是自定义的构造函数)时;

  4.当调用一个 member class 的 constructor,而它拥有一组参数时。

  这里总的需要留意两点:

  第一个:member object的初始化,最好放到初始化列表里面。若放置于构造器中,则会产生临时的object0,初始化之,在做赋值运算给object,然后object0自行销毁,期间耗时耗力。若置于初始化列表,则编译器会在构造函数中,user code之前,调用object的构造函数,予以初始化。

  第二个:初始化列表的初始化次序。初始化次序和member在类中的声明次序一致。相互关联的member,需要十分留意初始化列表中,其中依赖的次序。解决的办法:把其中一部分使用初始化列表初始化,而另一部分放置到构造函数中使用user code予以表达,这样即便次序存在依赖,也会只“先执行合成的,再执行user的code”。

【C++对象模型】第二章 构造函数语意学的更多相关文章

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

    默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...

  2. 深度探索c++对象模型 第二章

    1,c++转换函数:显示转换和隐式转换. 隐式转换为程序员提供了很大的变量.比如整形提升,普通类型转换为类类型(operator int())都为程序带来无尽的方便.试想,如果没有整形提升,一个sho ...

  3. 【深度探索C++对象模型 | 02】构造函数语意学

    默认构造函数的构造操作.拷贝构造函数额构造操作  注意:默认构造函数和拷贝构造函数在必要时的时候由编译器产生出来. 参考资料 关于默认构造函数的几个错误认识(四种情况下,编译器会生成默认构造函数)

  4. C++对象模型——Default Constructor的建构操作(第二章)

    第2章    构造函数语意学 (The Semantics of Constructor) 关于C++,最常听到的一个抱怨就是,编译器背着程序猿做了太多事情.Conversion运算符就是最常被引用的 ...

  5. 3-8《Ruby元编程》第二章对象模型

    <Ruby元编程> 第二章 对象模型 类定义揭秘inside class definitions: class关键字更像一个作用域操作符,核心作用是可以在里面随时定义方法. [].meth ...

  6. jQuery系列 第二章 jQuery框架使用准备

    第二章 jQuery框架使用准备 2.1 jQuery框架和JavaScript加载模式对比 jQuery框架的加载模式 <script> window.onload = function ...

  7. 第二章 NIO入门

    传统的同步阻塞式I/O编程 基于NIO的非阻塞编程 基于NIO2.0的异步非阻塞(AIO)编程 为什么要使用NIO编程 为什么选择Netty 第二章 NIO 入门 2.1 传统的BIO编程 2.1.1 ...

  8. 第二章 深入 C# 数据类型

    第二章 深入 C# 数据类型 1.封装又称信息隐藏,是指利用抽象数据类型将数据和数据的操作结合在一起,使其构成一个不可分割的独立实体,尽可能的隐藏内部的细节,只保留一些对外接口,使之于外部发生联系. ...

  9. 《驾驭Core Data》 第二章 Core Data入门

    本文由海水的味道编译整理,请勿转载,请勿用于商业用途.    当前版本号:0.4.0 第二章 Core Data入门 本章将讲解Core Data框架中涉及的基本概念,以及一个简单的Core Data ...

随机推荐

  1. Tengine/Nginx 安装

    原文出处:http://my.oschina.net/liuhuan0927/blog/604663 一.Tengine是什么 简介 Tengine是由淘宝网发起的Web服务器项目.它在Nginx的基 ...

  2. 20172330 2017-2018-1 《Java程序设计》第九周学习总结

    20172330 2017-2018-1 <程序设计与数据结构>第九周学习总结 教材学习内容总结 本周的学习包括两章内容,分别为异常和递归. 异常 错误和异常都是对象,代表非正常情况或者无 ...

  3. lintcode-34-N皇后问题 II

    34-N皇后问题 II 根据n皇后问题,现在返回n皇后不同的解决方案的数量而不是具体的放置布局. 样例 比如n=4,存在2种解决方案 标签 递归 思路 参考http://www.cnblogs.com ...

  4. LintCode-381.螺旋矩阵 II

    螺旋矩阵 II 给你一个数n生成一个包含1-n^2的螺旋形矩阵 样例 n = 3 矩阵为 [     [ 1, 2, 3 ],     [ 8, 9, 4 ],     [ 7, 6, 5 ] ] 标 ...

  5. iOS- 网络请求的两种常用方式【GET & POST】的区别

    GET和POST 网络请求的两种常用方式的实现[GET & POST] –GET的语义是获取指定URL上的资源 –将数据按照variable=value的形式,添加到action所指向的URL ...

  6. Winform 子窗体设置刷新父窗体

    方法1:所有权法 父窗体:Form1    子窗体:Form2 //Form1:窗体代码 //需要有一个公共的刷新方法 public void Refresh_Method() { //... } / ...

  7. C# 知识回顾 - 扩展方法解析

    在使用面向对象的语言进行项目开发的过程中,较多的会使用到“继承”的特性,但是并非所有的场景都适合使用“继承”特性,在设计模式的一些基本原则中也有较多的提到. 继承的有关特性的使用所带来的问题:对象的继 ...

  8. 数据存储到MySQL并返回新插入的id值

    当对数据库进行插入数据后,有时会需要刚插入的数据的id值,以作他用,整理如下: conn = pymysql.connect(, user=DB_USER, passwd=DB_PASSWORD, d ...

  9. SonarQube安装

    要求 至少1G以上内存,推荐为2G Java:Oracle JRE 7u75+,OpenJDK 7u75+ 数据库: Microsoft SQL Server 2008/2012/2014 MySQL ...

  10. Ubuntu 删除多余内核

    Ubuntu 删除多余内核 转载▼ 首先查询当前我们使用的是内核是那个版本别删错了. uname -a 第二: 查询系统中装了多少内核 dpkg --get-selections|grep linux ...