《深度探索c++对象模型》第六章笔记

news2024/11/24 8:25:47

非原创,在学习

6 执行期语意学(Runtime Semantics)

有这样一个简单的案例:

if (yy == xx.getValue()) 
{
    // ...
}

其中,xx和yy的定义为:

X xx;
Y yy;

class Y定义为:

class Y {
public:
    Y();
    ~Y();
    bool operator==(const Y&) const;
    // ...
};

class X定义为:

class X {
public:
    X();
    ~X();
    operator Y() const;        // conversion运算符
    X getValue();
    // ...
};

Y类重载了==运算符,形参是Y,在本例中笔记的是yy == xx.getValue();在X类中,有一个conversion运算符,将X对象转换为Y对象,这使得案例成立。

这里会产生一系列的临时变量。

(1)产生一个临时变量Class X object,放置getValue()的返回值

X temp1 = xx.getValue();

(2)产生一个临时变量class Y object,放置operator Y()的返回值

Y temp2 = temp1.operator Y();

(3)产生一个临时变量int object,放置equality(等号)运算符的返回值

int temp3 = yy.operator==(temp2);

最后,适当的destructor 将被施行于每一个临时性的class object身上。这导致我们的式子被转换为以下形式:

// C++伪码
// 以下是条件if(yy == xx.getValue()) ...的转换
{
    X temp1 = xx.getValue();
    Y temp2 = temp1.operator Y();
    int temp3 = yy.operator==(temp2);

    if (temp3) 
    {
        // ...
    }

    temp2.Y::~Y();
    temp1.X::~X();
}

略麻烦

6.1 对象的构造和解构( object Construction and Destruction )

一般而言,constructor和destructor的安插都如你所预期:

// c++伪码
{
    Point point;
    // 构造函数会被安插在这里
    // point.Point::Point()

    // ...

    // 析构函数会被安插在这里
    // point.Point::~Point();
}

构造函数的调用放在返回值之前。

全局对象(Global Objects )

有以下片段:

Matrix identity;

int main(void)
{
    // identity必须在此处被初始化
    Matrix m1 = identity;

    // ...    

    return 0;
}

C++保证,一定会在main()函数中第一次用到identity 之前,把identity构造出来,而在main()函数结束之前把identity摧毁掉。像identity这样的所谓global object 如果有constructor 和 destructor 的话,我们说它需要静态的初始化操作和内存释放操作.

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

int v1 = 1024;
int v2;

 初始化全局变量/全局对象

局部静态对象(Local Static Objects)

有以下案例:

const Matrix&
identity() {
    static Matrix mat_identity;

    // ...

    return mat_identity;
}

局部静态对象保证了什么样的语意?

mat_identity的constructor必须只能施行一次,虽然上述函数可能会被调用多次。
mat_identity的destructor必须只能施行一次,虽然上述函数可能会被调用多次。

编译器的策略之一就是,无条件地在程序起始( startup)时构造出对象来。然而这会导致所有的local static class objects都在程序起始时被初始化,即使它们所在的那个函数从不曾被调用过。因此,只在identity()被调用时才把mat_identity构造起来,是比较好的做法(现在的C++ Standard已经强制要求这一点)。我们应该怎么做呢?

以下就是我在 cfront之中的做法。首先,我导入一个临时性对象以保护mat_identity 的初始化操作。第一次处理identity()时,这个临时对象被评估为false,于是constructor会被调用,然后临时对象被改为true。这样就解决了构造的问题。而在相反的那一端,destructor也需要有条件地施行于mat_identity身上,但只有在mat_identity已经被构造起来时才算数。要判断mat_identity是否被构造起来,很简单。如果那个临时对象为true,就表示构造好了。困难的是由于cfront产生C码,mat_identity对函数而言仍然是local,因此我没办法在静态的内存释放函数〈 static deallocation function)中存取它。噢,伤脑筋!解决的方法有点诡异,结构化语言避之唯恐不及:我取出 local object的地址。(当然啦,由于object是 static,其地址在 downstream component中将会被转换到程序内用来放置 global object的 data segment中)。

最后,destructor必须在“与text program file (也就是本例中的 stat_0.c)有关联的静态内存释放函数( staic deallocation function)”中被有条件地调用。

对象数组(Array of Objects)

有以下数组定义:

Point knots[10];

如果 Point 既没有定义一个constructor也没有定义一个destructor,那么我们的工作不会比建立一个“内建(build-in)类型所组成的数组”更多,也就是说,我们只需配置足够的内存以储存10个连续的Point元素。

然而Point的确定义了一个default destructor,所以这个destructor必须轮流施行于每一个元素之上。一般而言这是经由一个或多个runtime library函数达成。

void*
vec_new(
    void* array,                         // 数组起始地址
    size_t elem_size,                    // 每个class object的大小
    int elem_count,                      // 数组中的元素数目
    void (*constructor)(void*),
    void (*destructor)(void*, char)
)

其中的constructor和 destructor参数是这个class的 default consructor和default destructor的函数指针。参数array带有的若不是具名数组(本例为knots)的地址,就是0。如果是0,那么数组将经由应用程序的new运算符被动态分配到heap中。参数elem_size表示数组中的元素数目。

Default Constructors 和数组

如果你想要在程序中取出一个constructor的地址,这是不可以的。当然啦,这是编译器在支持vec_new()时该做的事情。

举个例子,在 cfront 2.0 之前,声明一个由 class objects所组成的数组,意味着这个class必须没有声明constructors或一个default constructor(没有参数那种)。一个constructor不可以取一个或一个以上的默认参数值。这是违反直觉的,会导致以下的大错。

这一段没啥结论,就说了之前的一些错误写法

6.2 new 和delete 运算符

运算符new 的使用,看起来似乎是一个单一运算,像这样

int* pi = new int(5);

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

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

// 调用函数库中的new运算符
int* pi = _new(sizeof(int));

2 给配置得来的对象设立初始值

*pi = 5;

更进一步地,初始化操作应该在内存配置成功(经由new运算符)后才执行:

// new运算符的两个分离步骤
// given: int* pi = new int(5);

// 重写声明
int* pi;
if (pi == _new(sizeof(int))) {
    *pi = 5;            // 成功了才能初始化
}

好像用malloc和new的时候,还没出过错。。。。。。

delete运算符的情况类似。

对象的new也是这样,先申请空间,在初始化,如果处理异常,程序则会稍微复杂点

malloc申请出错返回NULL

new申请出错会抛出异常

针对数组的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* paggr = new simple_aggr[5];

vec_new()也不会被调用。为什么呢?因为simple_aggr并没有定义一个constructor或destructor,所以配置数组以及清除p_aggr数组的操作,只是单纯地获得内存和释放内存而已。这些操作由new和 delete运算符来完成就绰绰有余了。

然而如果class定义有一个default constructor,某些版本的vec_new()就会被调用,配置并构造class objects所组成的数组。例如这个算式:

Point3d* p_array = new Point3d[10];

通常会被编译为:

Point3d* p_array;
p_array = vec_new(0, sizeof(Point3d), 10,
                    &Point3d::Point3d,
                    &Point3d::~Point3d);

还记得吗,在个别的数组元素构造过程中,如果发生exception,destructor就会被传递给vec_new()。只有已经构造妥当的元素才需要destructor的施行,因为它们的内存已经被配置出来了,vec_new()有责任在exception发生的时候把那些内存释放掉。

在C++2.0版之前,将数组的真正大小提供给程序的delete运算符,是程序员的责任。

delete [size]p_array;

现在这样写

delete []p_array;

如果类中自己定义了构造函数和析构函数,在new 和 delete的时候,会调用构造函数和析构函数。

最好就是避免以一个base class指针指向一个derived class objects所组成的数组——如果derived class object比其 base 大的话。

Placement Operator new 的语意

有一个预先定义好的重载的( overloaded )new运算符,称为placement operator new。它需要第二个参数,类型为void*。调用方式如下:

Point2w* ptw = new(arena)Point2w;

其中arena指向内存中的一个区块,用以放置新产生出来的 Point2w object.这个预先定义好的 placement operator new的实现方法简直是出乎意料的平凡.它只要将“获得的指针(译注:上例的arena)”所指的地址传回即可:

void*
operator new(size_t, void* p)
{
    return p;
}

如果它的作用只是传回其第二个参数,那么它有什么价值呢?也就是说,为什么不简单地这么写算了(这不就是实际所发生的操作吗):

Point2w* ptw = (Point2w*)arena;

事实上这只是所发生的操作的一半而已。Placement new operator所扩充的另一半边是将Point2w constructor自动实施于arena所指的地址上:

// c++伪码
Point2w* ptw = (Point2w*)arena;
if (pte != 0)
{
    ptw->Point2w::Point2w();
}

这正是使placement operator new威力如此强大的原因。这一份码决定objects被放置在哪里;编译系统保证object的constructor会施行于其上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/841681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

目前Java后端就业前景怎么样?

前言 并不乐观,看看现在的就业形式就知道了,基本上是僧多粥少的情况,你可能会看到很多编程语言排行榜或者流行榜中Java的排名很高,如同下面这种: 看排名确实可以粗略的得知语言当下的流行度、使用率,但是它…

对齐控制大作战:align-content 和 align-items,到底谁才是真正的垂直大将?

🧑‍💼 个人简介:一个不甘平庸的平凡人🍬 🖥️ Node专栏:Node.js从入门到精通 🖥️ TS知识总结:十万字TS知识点总结 👉 你的一键三连是我更新的最大动力❤️!…

算法通过村第二关-链表黄金笔记|K个一组反转

文章目录 前言链表反转|K个一组翻转链表解题方法:头插法处理:穿针引线法处理: 总结 前言 提示:没有人天生就喜欢一种气味而讨厌另一种气味。文明的暗示而已。 链表反转|K个一组翻转链表 给你链表的头节点 head ,每 k…

毕业后想往开发上位机的方向发展,不知道怎么样?

上位机的薪资目前还可以,虽然不能比肩互联网,但是在所有行业中应该还算比较高的,二十几万的年薪比较容易。 还不错,最流行的开发方式有labview和C#,建议选C#。工控主要还是集中在长三角和珠三角,搞上位机很…

CentOS 安装 Jenkins

本文目录 1. 安装 JDK2. 获取 Jenkins 安装包3. 将安装包上传到服务器4. 修改 Jenkins 配置5. 启动 Jenkins6. 打开浏览器访问7. 获取并输入 admin 账户密码8. 跳过插件安装9. 添加管理员账户 1. 安装 JDK Jenkins 需要依赖 JDK,所以先安装 JDK1.8。输入以下命令&a…

为什么亚马逊购物车会丢失呢?如何找回来呢?

想要找回店铺购物车,必须先清楚购物车丢失的原因,只有找到原因,对症下药,才能以最快的速度找回。 一、亚马逊购物车丢失的原因: 1.listing新上架 通常来说,新上架的Listing,该Listing在亚马逊…

HIVE优化之不需要参数优化

#1.数据倾斜 什么是数据倾斜? 一部分数据多 一部分数据少 造成的结果: MR运行过慢 主要是shuffle和reduce过程慢 分组聚合导致数据倾斜 Hive未优化的分组聚合 方法1:在MAP端直接聚合(分组聚合优化),减少…

wxwidgets Ribbon使用wxRibbonToolBar实例

wxRibbonToolBar就是工具栏,一下是实现的效果,界面只是功能展示,没有美化 实现代码如下所示: MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(800, 600)) …

超越传统线程:探索Java Loom协程(纤程/虚拟线程)的无限潜力

《超越传统线程:探索Java Loom协程(纤程/虚拟线程)的无限潜力》 一、Java协程的发展历程 Java协程的发展历程可以概括为以下几个阶段: 1963年,协程的概念被正式提出,它的诞生甚至早于线程。2007年,Kilim项目发布&…

【C++】map和set在OJ中的应用

文章目录 前言1. 剑指 Offer : 复杂链表(带随机指针)的复制1.1 思路分析(利用map搞)1.2 AC代码 2. 前K个高频单词2.1 思路1AC代码2.2 思路2AC代码2.3 思路3AC代码 3. 两个数组的交集3.1 思路分析3.2 AC代码 前言 上一篇…

AI和ChatGPT:人工智能的奇迹

AI和ChatGPT:人工智能的奇迹 引言什么是人工智能?ChatGPT:AI的语言之王ChatGPT的工作原理ChatGPT的优势和挑战AI和ChatGPT的未来展望结论 引言 人工智能(Artificial Intelligence,简称AI)是一项令人兴奋的…

收集到大量的名片怎么转为excel?

来百度APP畅享高清图片 参加完展会或集体会议,是不是收了一大堆名片,保管起来超级麻烦,还容易丢三落四?别急,我们有办法!把名片转成电子版保存到电脑上就完美啦!但要是名片数量有点多&#xff0…

Linux文本三剑客之awk

目录 前言 awk 1.认识awk 2.使用awk 2.1语法 2.2常用命令选项 2.3awk变量 2.3.1内置变量 2.3.2自定义变量 2.4printf命令 awk例题 前言 awk、grep、sed是linux操作文本的三大利器,合称文本三剑客,也是必须掌握的linux命令之一。三者的功能都是…

什么是全局代理,手机怎么设置全局代理

目录 什么是全局代理 全局代理的优缺点 优点 缺点 手机怎么设置全局代理 注意事项 总结 在计算机网络和信息安全中,全局代理是一种常用的技术手段,用于将网络流量通过代理服务器进行转发和处理。本文将介绍什么是全局代理,探讨全局代理…

Stable Diffusion - Candy Land (糖果世界) LoRA 提示词配置与效果展示

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/132145248 糖果世界 (Candy Land) 是一个充满甜蜜和奇幻的地方,由各种各样的糖果和巧克力构成。在糖果世界,可以看到&…

el-select 三级联动

一、效果图 二、思路&#xff1a;先请求一级select数据&#xff0c;通过选中的id请求二级数据&#xff0c;以此类推&#xff01; 三、代码 <template><div><el-card><el-form :inline"true"><el-form-item label"一级">&l…

如何用正确的姿势监听Android屏幕旋转

作者&#xff1a;37手游移动客户端团队 背景 关于个人&#xff0c;前段时间由于业务太忙&#xff0c;所以一直没有来得及思考并且沉淀点东西&#xff1b;同时组内一个个都在业务上能有自己的思考和总结&#xff0c;在这样的氛围下&#xff0c;不由自主的驱使周末开始写点东西&…

安卓:UDP通信

目录 一、介绍 网络通信的三要素&#xff1a; &#xff08;1&#xff09;、IP地址&#xff1a; IPv4: IPv6: IP地址形式&#xff1a; IP常用命令&#xff1a; IP地址操作类: &#xff08;2&#xff09;、端口&#xff1a; &#xff08;3&#xff09;、协议: UDP协…

【图论】单源最短路

算法提高课笔记。&#xff08;本篇还未更新完… 目录 单源最短路的建图方式例题热浪题意思路代码 信使题意思路代码 香甜的黄油题意思路代码 最小花费题意思路代码 最优乘车题意思路代码 昂贵的聘礼题意思路代码 单源最短路的建图方式 最短路问题可以分为以下两类&#xff1a…

红队钓鱼技术之LNK快捷方式

简介 lnk文件是用于指向其他文件的一种文件。这些文件通常称为快捷方式文件&#xff0c;通常它以快捷方式放在硬盘上&#xff0c;以方便使用者快速的调用。lnk钓鱼主要将图标伪装成正常图标&#xff0c;但是目标会执行shell命令 步骤 1.编写shell命令 首先新建一个文本文件t…