【C++对象模型】第二章 构造函数语意学
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++对象模型】第二章 构造函数语意学的更多相关文章
- 《深度探索C++对象模型》第二章 | 构造函数语意学
默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...
- 深度探索c++对象模型 第二章
1,c++转换函数:显示转换和隐式转换. 隐式转换为程序员提供了很大的变量.比如整形提升,普通类型转换为类类型(operator int())都为程序带来无尽的方便.试想,如果没有整形提升,一个sho ...
- 【深度探索C++对象模型 | 02】构造函数语意学
默认构造函数的构造操作.拷贝构造函数额构造操作 注意:默认构造函数和拷贝构造函数在必要时的时候由编译器产生出来. 参考资料 关于默认构造函数的几个错误认识(四种情况下,编译器会生成默认构造函数)
- C++对象模型——Default Constructor的建构操作(第二章)
第2章 构造函数语意学 (The Semantics of Constructor) 关于C++,最常听到的一个抱怨就是,编译器背着程序猿做了太多事情.Conversion运算符就是最常被引用的 ...
- 3-8《Ruby元编程》第二章对象模型
<Ruby元编程> 第二章 对象模型 类定义揭秘inside class definitions: class关键字更像一个作用域操作符,核心作用是可以在里面随时定义方法. [].meth ...
- jQuery系列 第二章 jQuery框架使用准备
第二章 jQuery框架使用准备 2.1 jQuery框架和JavaScript加载模式对比 jQuery框架的加载模式 <script> window.onload = function ...
- 第二章 NIO入门
传统的同步阻塞式I/O编程 基于NIO的非阻塞编程 基于NIO2.0的异步非阻塞(AIO)编程 为什么要使用NIO编程 为什么选择Netty 第二章 NIO 入门 2.1 传统的BIO编程 2.1.1 ...
- 第二章 深入 C# 数据类型
第二章 深入 C# 数据类型 1.封装又称信息隐藏,是指利用抽象数据类型将数据和数据的操作结合在一起,使其构成一个不可分割的独立实体,尽可能的隐藏内部的细节,只保留一些对外接口,使之于外部发生联系. ...
- 《驾驭Core Data》 第二章 Core Data入门
本文由海水的味道编译整理,请勿转载,请勿用于商业用途. 当前版本号:0.4.0 第二章 Core Data入门 本章将讲解Core Data框架中涉及的基本概念,以及一个简单的Core Data ...
随机推荐
- Python中的相对导入语法
Python中支持相对导入语法,即可以相对于某一个package进行导入,具体语法如下: # 导入"./dir2/spam.py", .表示当前目录 from .dir2 impo ...
- 你代码写得这么丑,一定是因为你长得不好看----panboo第一篇博客
一.个人介绍 我叫潘博,软嵌162,学号1613072055. 以“panboo”名称混迹于各大开源IT论坛与博客. 除了编程,我的最大爱好是篮球与健身,热衷于各种IT技术与运动. 我做过的软件项目有 ...
- JavaScript初探系列之String的基本操作
1.字符串转换 字符串转换是最基础的要求和工作,你可以将任何类型的数据都转换为字符串,你可以用下面三种方法的任何一种: var myStr = num.toString(); // "19& ...
- 《剑指offer》---字符串的全排列
本文算法使用python3实现 1.问题一 1.1 题目描述: 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc ...
- Linux防火墙iptables学习
http://blog.chinaunix.net/uid-9950859-id-98277.html 要在网上传输的数据会被分成许多小的数据包,我们一旦接通了网络,会有很多数据包进入,离开,或者经过 ...
- 什么是RESTFUL协议?
1,restful是Representational State Transfer的缩写,翻译过来是表现层状态转移.我的理解是去掉访问文件的格式,比如去掉文件为html的.html,而是采用路径的方式 ...
- (转)Linux常用性能检测命令
一.uptime Uptime命令的显示结果包括服务器已经运行了多长时间,有多少登陆用户和对服务器性能的总体评估(load average).load average值分别记录了上个1分钟,5 ...
- cmd批处理中set /a和set /p的区别介绍
在 SET 命令中添加了两个新命令行开关: SET /A expression SET /P variable=[promptString]/p 是让你输入/a 是指定一个变量等于一串运算字符 什么参 ...
- 限制玻尔兹曼机(Restricted Boltzmann Machine)RBM
假设有一个二部图,每一层的节点之间没有连接,一层是可视层,即输入数据是(v),一层是隐藏层(h),如果假设所有的节点都是随机二值变量节点(只能取0或者1值)同时假设全概率分布满足Boltzmann 分 ...
- 【刷题】BZOJ 4503 两个串
Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有"?"字符,这个字符可以匹配任何字符. I ...