深度探索C++对象模型——2_构造函数语意学

对于编译器来说,更倾向于给错误找到一个尽可能合理的解释,而非直接报错。

1.Default Constructor 的构造操作

什么时候会合成出默认构造函数?
编译器需要的时候,为了保证程序的正常运行。而不是程序员需要的时候,因为默认的构造函数不能确保赋予所需的初值。

1.1 带有Default Constructor的Member Class Object

如果一个class没有任何的构造函数,但是复用了其他包含构造函数的类,那么编译器会在该类被调用的时候为该类构造出一个默认的构造函数。
编译器如何避免合成出多个默认构造函数?
将合成的构造,拷贝,析构,赋值都已inline的方式完成。一个inline函数为静态链接,不会被文件外看到。
如果该class有了构造函数,但是没有显示的调用复用的其他类的构造函数,怎么办?
编译器会将复用的其他类的构造函数按声明顺序插入道程序员自己写的构造函数之前。

1.2带有Default Constructor的Base Class

如果设计者提供了多个constructors,但没有默认构造函数,怎么办?
编译器会扩展现有的每一个constructors,将必要的defatult constructors的程序代码加进去,但不会合成一个新的default constructor。
按照声明顺序:类内对象,bc,用户自己写的构造函数合成

1.3 带有一个虚函数的类

另有两种情况,也需要合成出默认构造函数:

  • 1.class声明或继承一个虚函数
  • 2.class派生自一个继承串链,其中有一个或更多的虚基类。
    那么下面两个扩展获得会在编译期间发生:
  • 1.建立虚表,内放虚函数的地址
  • 2.建立虚指针,内涵类虚表的地址
  • 此外,虚函数的调用操作会被改写,改为通过虚指针和虚表索引的方式。
    对于未声明任何构造函数的类,编译器会合成一个默认构造函数,以正确初始化类的虚表和虚指针。

1.4 带有一个虚基类的类

不同编译器之间差异很大,但共同点在于必须使虚基类在其每一个派生类中的位置,使其能够在执行期准备完毕。
因为指针的对象可以改变。
(编译器必须改变执行存取操作的那些代码,使其能够延迟至执行期才能决定)
对于class定义的每一个constructor,编译器会安插那些允许每一个虚基类的执行期存取操作的代码。如果没有声明任何constuctor,编译器就必须为其合成一个。

总结

上述四种情况,编译器会为其合成出默认的构造函数。如果不是上述四种,也没有声明任何的构造函数,那么拥有的是隐式的没用的构造函数,实际上并不会被合成。
合成的构造函数中,基类的成员和成员类会被初始化。其他的所有nonstatic data member(如整形,整数指针,整数数组等)都不会被初始化。
故:
不是所有没有默认构造函数的类都会被合成出来一个。
编译器合成的默认构造函数不会显示设定类内每一个data member的默认值。

2. Copy Constructor 的构造操作

主要是用于处理除了位逐次拷贝之外的操作。
一个对象的内容作为另一个对象的初值的情况:

  • 1.对那个对象进行显示的初始化操作
  • 2.函数值传递
  • 3.函数值返回
    如果一个类没有声明拷贝构造函数,那么就会有隐式的声明或定义。C++将拷贝构造函数区分为trival和nontrival两种。只有nontrival的实例才会被合成于程序中。当class不展现为bitwise copy semantics(位逐次拷贝)时,才会进行合成。

2.1位逐次拷贝

不展现位逐次拷贝的情况:

  • 1.class内含一个member object,且其具有一个拷贝构造函数(无论是显示还是编译器合成)
  • 2.class继承自一个bc,且其存在一个拷贝构造函数(无论是显示还是编译器合成)
  • 3.class声明了一个或多个虚函数时
  • 4.class派生自一个继承串链,其中有一个或多个虚基类时
    前两种:编译器必须将member或bc的拷贝构造函数调用操作安插到合成的拷贝构造函数中去。
    这四种情况下:如果缺乏一个声明的拷贝构造函数,那么编译器为了处理将一个class object作为零一class object的初值的情况时,必须合成一个拷贝构造函数。

2.2 重新设定虚表的指针

第三种情况:
如果是同类之间拷贝:这种情况下bitwise copy是可行的。
将虚指针的值直接进行拷贝是安全的。
如果是将dc的值拷贝给bc:
那么合成出来的拷贝构造函数会显示的设定bc的虚指针指向bc的虚表,而不是直接拷贝。
综上,在第三种情况下,合成的拷贝构造函数对于虚指针让其指向自己被声明的类的虚表,而非被拷贝的类的虚表。

2.3 处理Virtual Base Class Subobject

第四种情况:
编译器必须合成一个拷贝构造函数,用以设定虚基类指针/offset的初值,对每一个members指向必要的memberwise初始化操作以及执行其他的内存相关操作。
如果一个初始化操作存在并保持“bitwise copy semantics”的状态时,编译器能够保证object有正确且相等的初始化操作时,应该压抑拷贝构造函数调用。

3.程序转化语意学

3.1显示的初始化操作

X x1(x0);
变为:
X x1;
x1.X::X(x0);
原有的定义被重写,初始化操作被移除,然后安插class的copy constructor。

3.2参数的初始化

指把一个class object 当作参数传递给一个函数:

  • 法1:导入临时性的Object,然后调用拷贝构造函数将其初始化,函数以引用的形式获得该临时性object。该class的destructor会在函数完成后被调用,去除临时性的object
  • 法2:以“拷贝建构”的方式把实际参数直接建构在其应该的位置上,该位置视函数活动范围不同,记录在程序堆栈中。在函数返回前,析构函数被调用

3.3 返回值的初始化

  • 1.首先加上一个额外参数,类型是class object的一个reference。这个参数用来放置被“拷贝建构”而得的返回值。
  • 2.在return指令前安插一个拷贝构造的调用操作,将欲传回的object的内容当作上述新增参数的初值。
  • 3.改写函数,使其不返回任何值。

3.4 摘要

拷贝构造函数的应用,迫使编译器会对程序代码进行部分优化,尤其是当一个函数以值传递的方式传回一个class object,而该类有一个copy constructor(无论是显示的还是合成的).

4.成员们的初始化队伍

在写下一个构造函数时,有机会设定class members的初值。

  • 法1:经过函数初始化列表
  • 法2:构造函数体内部
    下列情况下必须使用法1:
  • 1.初始化一个reference member时
  • 2.初始化一个const member 时
  • 3.调用一个bc的constructor,而它具有一组参数时
  • 4.调用一个member class的constructor,而它具有一组参数时。
    4:如果在函数体内部实现,编译器会构造一个临时性的
    object,然后将其赋值给对应的members object,然后再摧毁临时性的那个。
    编译器在操作初始化列表时,会按照其在class内部声明的顺序进行处理,然后在函数体的前部插入初始化操作。
    如果要在成员初始化列表中调用函数,且需要传递参数。请使用存在于构造函数内的一个成员,而不是使用成员初始化列表中的成员来给一个成员设置初值,因为不知道这个函数对于这个对象的依赖性有多高。
    members function的使用是合法的,这是因为与此object相关的this指针已经建构妥当。(但其使用的members不一定被初始化了)
    总结:
    编译器会对初始化列表一一处理并排序,以反映出members的声明顺序。安插代码到constructor内部,并置于任何explicit user code之前。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值