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. lintcode-170-旋转链表

    170-旋转链表 给定一个链表,旋转链表,使得每个节点向右移动k个位置,其中k是一个非负数 样例 给出链表1->2->3->4->5->null和k=2 返回4-> ...

  2. iOS开发给UIView添加动画Animation

    self.testView需要添加动画的view 1.翻转动画 [UIView beginAnimations:@"doflip" context:nil]; [UIView se ...

  3. Qt在VS(Visual Studio)中使用

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt在VS(Visual Studio)中使用     本文地址:https://www.te ...

  4. idea导出jar包

    在File->Project Structure->Artifacts,如图:  然后: 点击Apply,OK. 跳出去就可以看到多了META-INF文件夹: 然后build项目,就可以看 ...

  5. dedecms给原模型添加新字段

    1.进入dedecms后台 2.点击核心=>频道模型=>内容模型管理(在这里可以看到dedecms预设的模型设置) 3.选中我们需要的模型,点击更改,跳入以下页面 4.点击字段管理(可以看 ...

  6. Delphi DBGrid双击事件、单元格操作

    1.得到当前格子中的内容:DBGrid1.Fields[DBGrid1.SelectedIndex].DisplayText;把DBGrid1.SelectedIndex改为你所希望引用的字段就可以了 ...

  7. SSE:服务器发送事件,使用长链接进行通讯 基础学习

    HTML5中新加了EventSounce对象,实现即时推送功能,可以从下面连接中学习, http://www.kwstu.com/ArticleView/kwstu_20140829064746093 ...

  8. RT-thread内核对象标志flag总结

    一.内核标志flag 在内核对象控制块中有一个标志成员flag(rt_uint8_t flag; ),这个标志在不同有内核对象中具有不同的含义.rt-thread的内核对象有定时器.线程.信号量.互斥 ...

  9. 【bzoj2929】[Poi1999]洞穴攀行 网络流最大流

    题目描述 洞穴学者在Byte Mountain的Grate Cave里组织了一次训练.训练中,每一位洞穴学者要从最高的一个室到达最底下的一个室.他们只能向下走.一条路上每一个连续的室都要比它的前一个低 ...

  10. [CF551E]GukiZ and GukiZiana

    题目大意:一个长度为$n(n\leqslant5\times10^5)$的数组,有两个操作: $1\;l\;r\;x:$把区间$[l,r]$加上$x$ $2\;x:$询问$x$第一次出现和最后一次出现 ...