深度探索C++对象模型笔记---第五章&&第六章

第五章 构造、析构、拷贝语意学

纯虚函数的存在
纯虚函数可以被定义和调用,不过只能被静态调用。纯虚析构函数,类的设计者一定要定义。因为每一个派生类的析构函数会被扩张,以静态的方式调用每一个上一层的基类的析构函数。只要缺少了任何一个类的析构函数那么就会发生链接失败。最好不要把virtual deconstructor 声明为pure。

虚函数是否需要声明为const
作者建议不使用const,可能派生类要在虚函数中修改数据成员

不要将所有的成员函数都定义成虚函数,因为虚函数的实现会降低性能的

5.1 “无继承”情况下的对象构造
为继承做准备
存在虚函数的类,书中的例子
虚函数的引入不仅仅是每个类对象增加了一个vptr,而且会引发编译器对类产生膨胀作用
我们所定义的构造函数,编译器会附加一些代码,以便vptr初始化
合成一个copy constructor 和一个copy assignment operator,这个操作都不是trival。如果point类对象被初始化或以一个派生类对象赋值,bitwise操作就存在问题了,vptr设置出错

C++编译器要求编译器尽量延迟nontrivial members的实际合成操作,直到遇到使用场合为止
如果在你的设计中存在很多以传值的方式返回局部类对象,提供一个拷贝构造函数就比较合理。

5.2 继承体系下的对象构造
constructor
编译器做的扩充操作
(1)成员初始化列表中的数据成员初始化操作放入constructor本体中,以成员声明顺序存放
(2)如果有一个成员没有出现在成员初始化列表中,但是有默认的构造函数,那么这个默认构造函数必须被调用
(3)在那之前,如果类对象有虚表指针,他们必须被设定初值
(4)在那之前,所有上一层的基类构造函数必须被调用,以基类的声明顺序为顺序
(5)在那之前,所有虚基类构造函数被调用,从左到右,从深到浅

基类构造函数的调用
位于派生类构造函数初始化列表中的基类构造函数就调用,否则调用默认的构造函数或者(默认的memberwise copy constructor)

虚拟继承
传统的构造函数的扩充现象没有用,因为虚基类的共享性
虚基类的构造函数被调用有着明确的定义,只有当一个完整的类对象被定义出来时,它才会被调用。如果对象只是某个完整对象的子对象,它就不会被调用

vptr初始化语意学
vptr何时初始化?在基类构造函数调用之后,在程序员提供的代码或者成员初始化列表成员初始化操作之前

构造函数执行算法如下:

  • 在派生类的构造函数中,所有的虚基类及上一层的基类的构造函数会被调用
  • 上述完成后,对象的虚表指针会被初始化,指向相关的虚表
  • 如果有成员初始化列表,将在构造函数体内扩展开来,这必须在虚表指针被设定之后才进行,以免有一个虚成员函数被调用
  • 最后,执行程序员提供的代码

5.3 对象复制语意学
只有在默认行为导致的语意不安全时,才需要设计一个copy assignment operator
一个类对于默认的copy assignment operator,在下列情况下不会表现出bitwise copy
(1)当类中的有一个成员对象,且这个对象所对应的类中有一个cap(copy assignment operator)
(2)当类中的基类有一个cap
(3)当类声明了任何的虚函数时(因为vptr)
(4)当类继承自一个虚基类

5.4 对象的效能
析构语意学
如果类中没有定义析构函数,只有当member object/base class拥有析构函数,编译器才会自动合成一个析构函数。否则,不会合成析构函数

第六章 执行期语意学

本章一开头列举的代码,发生很多的转变,以及生成了许多临时变量。本章就是要揭开执行期发生的一些转换
6.1 对象的构造和析构
把object尽可能放置在使用它的那个程序区段的附近,节省非必要的对象产生操作和摧毁操作。这个建议,在effective C++书中也提到过

全局对象
全局对象需要静态的初始化操作和内存释放操作。
全局对象如果显示的给它制定一个 值,这个对象就以这个值作为初值,否则,对象配置的内存内容为0

这本书中还提到了cfront编译器利用munch方法静态初始化和内存释放。对每一个需要静态初始化的对象产生一个_sti函数静态初始化,_std函数静态内存释放。然后一个_main()函数调用所有的_sti函数,exit()函数调用_std函数。在main函数中加入_main()和exit()两个函数,_main()做为第一个指令。

局部静态对象
在front中,对静态局部对象导入一个临时性对象保护局部静态对象的初始化操作。当第一调用这个静态对象所在的函数时,这个临时对象为false,初始化后,变为true。后面再调用局部静态对象所在的函数时,就不会重复初始化这个静态对象了。

对象数组

Point knots[10];

Point 没有定义构造函数和析构函数,那么这个工作只要配置存储10个连续的point元素的内存
当定义了构造函数的情况下,在cfront中,使用一个vec_new()的函数,产生类对象构造的数组。使用vec_delete()进行析构

默认的构造函数和数组
对于类提供的构造函数有一个或一个以上的默认参数值时,数组是如何构造的呢?
cfront产生一个内部的stub constructor,没有参数。这个函数内部调用类的构造函数,并将默认参数值显示指定
只有当类对象数组存在时,stub实例才会被产生以及被使用

6.2 new 和 delete 操作
运算符new由两个步骤完成
(1)配置内存
(2)将配置的来的对象设置初值

delete pi;
pi所指的对象因为delete操作而结束,把pi当做指针使用是可以的。因为地址本身任然可以代表一个合法的程序空间,就像一个void*指针

256页的代码
有两个地方值得注意

当size为0,size=1,因为new调用会返回一个独一无二的指针,所以分配1byte的内存空间。
允许程序员编写自己的_new_handler()函数

针对数组的new语意

int *p_array=new int[5];

vec_new()不会被调用,它的主要功能是把默认构造函数施行于类对象所组成的数组的每一个元素身上。new运算符函数会被调用

delete []p_array;

寻找数组维度,对于delete运算符效率带来极大的冲击,所以只有在中括号出现的时候才会寻找数组的维度。

Placement Operator new的语意
有一个预先定义好的重载的new运算符,称为placement operator new。

6.3临时性对象

T c=a+b;(1)
T operator +(const T&,const T&);
T T:: operator +(const T&);
c=a+b;(2)

在第一种情况下,一般不会产生一个临时性对象
而在第二种情况下,会产生临时性对象,其产生的情况如下:

T temp;
temp.operator+(a,b);
c.operator=(temp);
temp.T::~T();

临时性对象被摧毁,应该是对完整表达式求职过程中的最后一个步骤,这个完整表达式造成临时对象的产生

如果一个临时性对象被绑定与一个reference,对象将残留,直到被初始化reference的生命结束,或直到临时对象的声明范畴结束,看哪一种情况先发生。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值