执行期语意学_执行期学-程序员宅基地

技术标签: new  执行期语意  Inside C++ object model  

6.1 对象的构造和析构

一般而言,constructor和destructor的安插都如你预期的那样:
{
	Point point;
	//point.Point::Point()一般而言放在这里
	...
	//point.Point::~Point()一般而言放在这里
}
如果一个区段(以{}括起来的区域)或函数中由一个以上的离开点,情况就不同了。Destructor必须被放在每一个离开点(当时object还存活)之前,比如:
{
	Point point;
	//constructor在这里行动
	switch (int(point.x())) {
	case -1:
		//destructor在这里
		return;
	case 0:
		//destructor在这里
		return;
	case 1:
		//destructor在这里
		return;
	case default:
		//destructor在这里
		return;
	}
	//destructor在这里
}
在这个例子中,point的destructor必须在switch指令4个出口的return操作前被生成出来。另外也很有可能在这个区段的结束符号(右大括号)之前被生成出来。
再看下面这个例子:
{
	if (cache)
		return 1;
	Point xx;
	//xx的constructor在这里行动
	if (xx == value)
		//xx的destructor在这里行动
		return 1;
	else
		//xx的destructor在这里行动
		return 0;
}
Destructor调用操作必须放在最后两个return指令之前,但是却不必放在第一个return之前,因为那时object尚未被定义出来。
一般而言会把object尽可能放置在使用它的哪个程序区段附近,这么做可以节省非必要对象产生操作和摧毁操作。以本例为例,如果在检查cache之前就定义了Point object,那就不够理想。很多次需要在使用C++的时候,仍然习惯把所有objects放在函数或某个区段的起始处(include me )。

全局对象

如果有以下程序片段:

Matrix identity;
main()
{
	//identity必须在此处被初始化
	Matrix m1 = identity;
	return 0;
}
C++保证,一定会在main()函数中第一次用到identity之前,把identity构造出来,而在main()函数结束之前把identity摧毁掉。像identity这样的所谓的global obgect如果有constructor和destructor的话,我们就说它需要静态的初始化操作和内存释放操作。

 C++程序中所有的global objects都被放置在程序的data segment中.如果明确指定给它一个值,object将以该值为初值.否则object所配置到的内存内容为0.因此在下面这段码中:

int v1 = 1024;
int v2;
 v1和v2都被配置于程序的data segment,v1值为1024,v2值为0(这和C略有不同,C并不自动设定初值)。虽然 class object在编译时期可以被放置于data segment中并且内容为0,但constructor一直要到程序激活(startup)时才会实施.必须对一个"放置于program data segment中的object的初始化表达式"做评估,这正是为什么一个object需要静态初始化的原因。

下面是静态初始化方法:

 1.为每一个需要静态初始化的档案产生一个_sti()函数,内带必要的constructor调用操作或 inline expansions.例如前面所说的identity对象会在matrix.c中产生出下面的_sti()函数(sti可能是 static initialization的缩写):

__sti__matrix_C__identity() {
    identity.Matrix::Matrix();        // 这就是 static initialization
}
 其中matrix_c是文件名编码,_identity表示文件中所定义的第一个 static object.在__sti之后附加上这两个名称,可以为可执行文件提供一个独一无二的识别符号.
 2.类似情况,在每一个需要静态的内存释放操作(static deallocation)的文件组宏,产生出一个__std()函数(std可能是 static deallocation的缩写),内带必要的destructor调用操作,或是其 inline expansions.在例子中会有一个__std()函数被产生出来,针对identity对象调用Matrix destructor.
 3.提供一组runtime library "munch"函数:一个_main()函数(用以调用可执行文件中的所有的__sti()函数),以及一个exit()函数(以类似方式调用所有的__std()函数).

局部静态对象

  假设有以下程序片段:

const Matrix &identity() {
    static Matrix mat_identity;
    // ...
    return mat_identity;
}
Local static class object保证了什么样的语意?
mat_identity的constructor必须只能施行一次,虽然上述函数可能会被调用多次.
mat_identity的destructor必须只能施行一次,虽然上述函数可能会被调用多次.
编译器的策略之一就是,无条件地在程序起始(startup)时构造出对象,然而这会导致所有的 local static class objects都在程序起始时被初始化,即使它们所在的那个函数从不曾被调用过.因此,只在identity()被调用时才把mat_identity构造出来,是比较好的做法.
首先导入一个临时性对象以保护mat_identity的初始化操作.第一次处理identity()时,这个临时对象被评估为 false,于是constructor会被调用,然后临时对象被改为 true.这样就解决了构造的问题,而在相反的那一端,destructor也需要有条件地施行与mat_identity身上,但只有在mat_identity已经被构造起来时才算数.要判断mat_identity是否被构造出来,很简单, 如果那个临时对象为 true,就表示构造好了.

对象数组

   假设有下列的数组定义:

Point knots[10];
    需要完成什么呢?如果Point既没有定义一个constructor也没有定义一个destructor,那么工作不会比建立一个"内建(build-in)类型所组成的数组"更多,也就是说,只需配置足够的内存以储存10个连续的Point元素.
    然而Point的确定义了一个 default destructor,所以这个destructor必须轮流施行于每一个元素上.一般而言这是经由一个或多个runtime library函数达成.在cfront中,使用一个被命名为vec_new()的函数,产生出以 class objects 构造而成的数组.新近的编译器则提供两个函数一个用来处理"没有virtual base class"的 class,另一个用来处理"内带virtual base class"的 class.后一个函数通常被称为 vec_vnect.函数类型通常如下:
void *vec_new() {
    void *array,        // 数组起始地址
    size_t elem_size,    // 每一个class object的大小
    int elem_count;        // 数组中的元素数目
    void (*constructor)(void *),
    void (*destruction)(void *, char)
}
    其中constructor和destructor参数是这个 class 的default construct和default destructor的函数指针.
    参数array带有的若不是具名数组
(本例为knots) 的地址,就是0.如果是0,那么数组将经由应用程序的 new 运算符,被动态配置于heap中.
     参数elem_size表示数组中的元素大小(书上翻译为元素数目可能有误).在vec_new中,constructor施行于elem_count个元素上.对于支持exception handling的编译器而言,destructor的提供是必要的.下面是编译器可能针对10个Point元素所做的vec_new()调用操作:
Point knots[10];
vec_new(&knots, sizeof(Point), 10, &Point::Point, 0);
    如果Point也定义了一个destructor,当knots的生命结束时,该destructor也必须施行于那10个Point元素上.这是一个经由一个类似的 vec_delete()的runtime library函数完成的,其 函数类型如下:
void *vec_delete{
    void *array,        // 数组起始地址
    size_t elem_size,    // 每一个class object的大小
    int elem_count,        // 数组中的元素数目
    void (*destructor)(void *, char)
}
    有些编译器会另外增加一些参数,用以传递其他数值,以便能有条件地导引vec_delete()的逻辑,在vec_delete()中,destructor被施行于elem_count个元素上.
    如果程序员提供一个或多个明显初值给一个由 class objects组成的数组,像下面这样,会如何?
Point knots[10] = {
    Point,  //明显获得初值的
    Point(1.0, 1.0, 0.5),  //明显获得初值的元素
    -1.0  //明显获得初值的元素
};
    对于那些明显获得初值的元素,vec_new不再有必要.对于那些尚未被初始化的元素,vec_new()的施行方式就像面对"由class elements组成的数组,而该数组没有explicit initialization list"一样,因此上一个定义很可能被转换为:
Point knots[10];
// 明确地初始化前3个元素
Point::Point(&knots[0]);
Point::Point(&knots[1], 1.0, 1.0, 0.5);
Point::Point(&knots[2], -1.0, 0.0, 0.0);
// 以vec_new初始化后7个元素
vec_new(&knots+3, sizeof(Point), 7, &Point::Point, 0);

6.2 new和delete运算符

运算符new的使用,看起来似乎是个单一运算,比如

int *pi=new int(5);

但事实上它是由两个步骤完成的:

1.通过适当的new运算符函数实例,配置所需的内存:

//调用函数库中的new运算符

int *pi=__new(sizeof(int));

2.将配置得来的对象设立初值:

*pi=5;

delete运算符的情况类似,delete pi;

相当于是:

if(pi!=0)

__delete(pi);


注意:在C++中,new运算符实际上总是以标准的C malloc()完成,虽然并没有规定一定得这么做不可。相同情况下,delete运算符也总是以标准的C free()完成。

针对数组的new语意

int *p_array=new int[5];
vec_new()不会真正被调用,因为它的主要功能是把default constructor施行于class objects所组成的数组的每一个元素身上。倒是new运算符函数会被调用:
int *p_array=(int*)__new(5*sizeof(int));
相同情况,如果我们写:
struct simple_aggr
{
	float f1, f2;
};
simple_aggr *p_aggr = new simple_aggr[5];
vec_new()也不会被调用,因为simple_aggr并没有定义一个constructor或destructor,所以配置数组以及清除p_aggr数组的操作,只是单纯地获得内存和释放内存而已。这些操作由new和delete运算符来完成就行了。
然而,如果class定义了一个default construct,vec_new()就会被调用,配置并构造class objects所组成的数组。

在delete数组时,可以这样写:
delete [] p_array;
在编译器内部,应如何记录元素个数呢?一个明显的方法是为vec_new()所传回的每一个内存区块配置一个额外的word,然后把元素个数包藏在那个word之中。通常这种被包藏的数值称为cookie。另一种是维护一个联合数组,用于放置指针及大小。

Placement Operator new的语意

placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

调用方式如下:

Point2w *ptw=new(arena)Point2w;

函数形式是:

void *operator new(size_t,void *p)
{	
	return p;
}
事实上这只是所发生操作的一半而已。另外一半无法由程序员产生出来。

Placement new operator所扩充的另一半是将Point2w constructor自动实施于arena所指的地址上:

Point2w *ptw = (Point2w*)arena;
if (ptw != 0)
ptw->Point2w::Point2w();
这正是placement operator new牛逼的地方,这段代码决定objects被放置在哪里;编译器保证object的constructor会施行于其上。

注意下面这个问题:

void fooBar() {
	Point2w *p2w = new(arena)Point2w;
	p2w = new(arena)Point2w;
}
如果placement operator在原已存在的一个object上构造新的object,而该既存的object有destructor,这个destructor并不会被调用。调用gaidestructor的方法之一是将那个指针delete掉。不过这是个严重的错误:

delete p2w;

p2w=new(arena)Point2w;

如上操作,delete运算符会发生作用,这是我们所期待的。但是它也会释放由p2w所指的内存,这却不是我们所希望的,因为后面那条语句马上就会用到p2w了。因此,正确的方法是显示地调用destructor并保留存储空间以便再次使用。

p2w->~Point2w;

p2w=new(arena)Point2w;





版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wk_bjut_edu_cn/article/details/80172132

智能推荐

softmax交叉熵损失函数深入理解(二)_softmax损失函数-程序员宅基地

文章浏览阅读7.5k次,点赞9次,收藏30次。前期博文提到经过两步smooth化之后,我们将一个难以收敛的函数逐步改造成了softmax交叉熵损失函数,解决了原始的目标函数难以优化的问题。Softmax 交叉熵损失函数是目前最常用的分类损失函数,本博文继续学习Softmax 交叉熵损失函数的改进,详细的理论参考论文《基于深度学习的人脸认证方法研究》,这篇论文真的太棒了,是我见过最优秀的专门针对损失函数进行深入研究的杰作。..._softmax损失函数

网络编程——多路复用_多路复用功能-程序员宅基地

文章浏览阅读206次。网络编程中多路复用的用法_多路复用功能

matplotlib(2)_matplotlib x 轴等距-程序员宅基地

文章浏览阅读136次。保存图片:作用:将绘图保存到图片绘图1.条形图 垂直条形图 原型:ax.bar(x,height,width=0.8,bottom=None, *,align='center',data=None,**kwargs)import mathimport matplotlib.pyplot as pltimport numpy as np%matplotlib inlinefig=plt.figure()ax=fig.add_a..._matplotlib x 轴等距

ICA:独立成分分析-程序员宅基地

文章浏览阅读965次,点赞21次,收藏29次。如果独立成分中有两个以上的高斯成分,用标准的独立成分分析来处理这样的数据是不可能的。另一个假设是信号的非高斯性,现实世界的许多信号,诸如绝大多数的语音信号和图像信号即是服从非高斯分布的这个假设的可应用性,带来了独立成分分析的重要特征,即。ICA中:去除各观测信号之间的相关性,从而简化了后续独立分量的提取过程,而且,通常情况下,比不对数据进行白化处理相比,算法的收敛性较好。),从而求得位置参数,计算机更适合迭代,在计算量较小时,直接求导得到解析解速度占优,而在深度学习中全部是以矩阵的方式进行求导,且。

拆分列为行-程序员宅基地

文章浏览阅读834次。create table tb3(id int identity(1,1) primary key clustered, l_name varchar(100))--tb3数据id l_name

2021最新Java框架体系架构面试题-java面试简历作假-程序员宅基地

文章浏览阅读62次。前言过去十多年互联网产业的高速发展,在给社会带来深刻变革的同时,也催生了服务架构的演进:从传统的单体应用到面向服务的SOA,再到现今主流的微服务架构,而 Apache Dubbo就是微服务领域中的先行者和佼佼者。Apache Dubbo是阿里巴巴于2011年开源的一款高性能Java RPC框架,在这方面阿里巴巴是有强力发言权的,不如跟着阿里的大佬来学一学这份业内公认的**“大师级Dubbo实战笔记”**,点赞+关注支持一下!一:打破狭窄的技术视野①真实项目对外开发系统性讲解②例如电商、教育行业、

随便推点

Linux iverilog编译与波形显示_iverilog 生成lxt-程序员宅基地

文章浏览阅读301次。【代码】Linux iverilog编译与波形显示。_iverilog 生成lxt

6年测试开发工程师职业生涯感悟——写给还在迷茫的朋友_测试工程师与公司同风雨的文章-程序员宅基地

文章浏览阅读271次。蓦然回首,软件测试风风雨雨的这6年,起初每天的工作只是鼠标点点点,那时候问我测试用例咋写,我还真不知道,闲的时候真的怀疑自己的存在价值,拿着7000的工资,飘荡在繁华的深圳,吃不饱也饿不死,未来一片茫然……时间荏苒,工作越久越能体会测试才不是这么简单,假如只会点点鼠标,并不能走得长远,工资也渐渐地无法满足生活需求,恍然明白,对于一个测试员而言,自己没有一个很好的框架,完全跟着感觉走,根本写不出好的测试用例。测试绝对不是一个人闷头干,避免不了与人打交道,其中与开发沟通交流得最多,倘若遇到好的开发,流程是_测试工程师与公司同风雨的文章

PHP 使用 curl问题解决 报错 Call to undefined function curl_init() ;_php7.3 call to undefined function curl_init()-程序员宅基地

文章浏览阅读1.1k次。因公司需求在自学Php其中一小部分GatewayWorker,学习过程中使用get,或者post请求网络数据, 通过Curl 但是报错,老痛苦了,幸公司有人指点。把解决问题方法分享给大家,希望可以对大家有所帮助 .第一步:. 在php 安装目录低下找到 php-ini 如果没有 找到 php.ini-production 拷贝到windows低下 ,重新命名php-ini ..._php7.3 call to undefined function curl_init()

计算机-程序员宅基地

文章浏览阅读244次。## 标题我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会我不会...

大数据技术之Hadoop(入门)_cdp hadoop-程序员宅基地

文章浏览阅读143次。1)Hadoop是一个由Apache基金会所开发的分布式系统基础架构;2)主要解决,海量数据的存储和海量数据的分析计算问题;3)广义上来说,Hadoop通常是指一个更广泛的概念--Hadoop生态圈;Hadoop Distributed File System,简称HDFS,是一个分布式文件系统。Yet Another Resource Negotiator简称YARN ,另一种资源协调者,是Hadoop的资源管理器。MapReduce将计算过程分为两个阶段:Map和Reduce。_cdp hadoop

关于定位_meta+头部定位-程序员宅基地

文章浏览阅读509次。关于定位emmm,怎么开头那。。。好纠结。这是一个关于定位的总结,恩,就是那种网上一搜一大片的那种,加上了一些我自己的话。定位测试的代码<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <tit_meta+头部定位

推荐文章

热门文章

相关标签