条款8:了解各种不同意义的new和delete
有时候我们觉得,C++的术语仿佛是要故意让人难以理解似的。
这里就有一个例子:请说明new operator 和operator new 之间的差异(译注:本书所说的new operator,即某些C++教程如C++ Primer 所谓的new expression)
当你写出这样的代码:
string *ps= new string("Memory Management");
你所使用的 new 是所谓的new operator。
这个操作符是由语言内建的,就像sizeof那样,不能被改变意义,总是做相同的事情。
它的动作分为两方面。
- 第一,它分配足够的内存,用来放督某类型的对象。以上例而言,它分配足够放置一个string 对象的内存。
- 第二,它调用一个constructor,为刚才分配的内存中的那个对象设定初值。
new operator总是做这两件事,无论如何你不能够改变其行为。
你能够改变的是用来容纳对象的那块内存的分配行为。也就是上面的第一步
new operator 调用某个函数,执行必要的内存分配动作,你可以重写或重载那个函数,改变其行为。这个函数的名称叫做operator new。头昏了吗?真的,我说的是真的。
函数operator new 通常声明如下:
void * operator new(size_t size);
其返回值类型是void*。此函数返回一个指针,指向一块原始的、未设初值的内存(如果你喜欢,可以写一个新版的operator new,在其返回内存指针之前先将那块内存设定初值。只不过这种行为颇为罕见就是了)。
函数中的size_t 参数表示需要分配多少内存。
你可以将operator new 重载,加上额外的参数,但第一参数的类型必须总是 size_t(如何撰写 operator new,相关信息请参考条款E8~E10)
吸中协从不想到要直接调用operator new,但如果你要,你可以像调用任何其他函数一样地调用它:
void *rawMemory = operator new(sizeof(string));
这里的operator new将返回指针,指向一块足够容纳一个string对象的内存。
和malloc一样,operator new的唯一任务就是分配内存。它不知道什么是constructors, operator new只负责内存分配。
取得 operator new 返回的内存并将之转换为一个对象,是new operator 的责任。
当你的编译器看到这样一个句子:
string *ps= new string("Memory Management");
它必须产生一些代码,或多或少会反映以下行为(见条款E8和条款E10,以及发表于C/C++ Users Journal, April1998 的文章《Counting Objects inC++》中的方块内容)
void *memory =
operator new(sizeof(string));//取得原始内存(raw memory)。用来放置一个string对象。call string::string("Memory Management")//将内存中的对象初始化。
on *memorystring *ps=
static_cast<string*>(memory);//让ps指向新完成的对象。
注意上述第二步骤涉及“调用一个constructor”,身为程序员的你没有权力这么做。
然而你的编译器百无禁忌,可以为所欲为。这就是为什么如果你想要做出一个heap-based object,一定得使用new operator 的原因:你无法直接调用“对象初始化所必需的constructor”
(尤其它可能得为重要成分vtbl设定初值,见条款24)。
Placement new
有时候你真的会想直接调用一个constructor。
针对一个已存在的对象调用其constructor 并无意义,因为 constructors 用来将对象初始化,而对象只能被初始化一次。
但是偶尔你会有一些分配好的原始内存,你需要在上面构建对象。有一个特殊版本的operator new,称为placement new,允许你那么做。
下面示范如何使用 placement new:
class Widget {
public:
widget(int widgetsize);
//...
}widget * constructWidgetInBuffer (void *buffer, int widgetSize)
{
return new (buffer) Widget(widgetsize);
}
此函数返回指针,指向一个widget object,它被构造于传递给此函数的一块内存缓冲区上。
当程序运行到shared memory 或memory-mapped I/O,这类函数可能是有用的,因为在那样的运用中,对象必须置于特定地址,或是置于以特殊函数分配出来的内存上(条款4列有placement new的另一个运用实例)
在 constructWidgetInBuffer 函数内部,唯一一个表达式是,
new (buffer) Widget(widgetSize)
乍见之下有点奇怪,其实不足为奇,这只是new operator 的用法之一,其中指定一个额外自变量(buffer)作为 new operator “隐式调用operator new”时所用。
于是,被调用的operator new除了接受“一定得有的size_t 自变量”之外,还接受了一个void*参数,指向一块内存,准备用来接受构造好的对象。这样的operator new 就是所谓的placement new,看起来像这样:
void * operator new (size_t,void *location)//注意size_t后面没名字
{
return location;
}
似乎比你预期得更简单,但这便是placement new必须做的一切。
毕竟operator new的目的是要为对象找到一块内存,然后返回一个指针指向它。
在placement new的情况下,调用者已经知道指向内存的指针了,因为调用者知道对象应该放在哪里。因此placement new 唯一需要做的就是将它获得的指针再返回。
至于没有用到(但一定得有)的size_t参数,之所以不赋予名称,为的是避免编译器发出“某物未被使用”的警告(见条款6)Placement new 是C++标准程序库(见条款E49)的一部分。
欲使用 placement new,你必须用#include <new>。如果你的编译器尚未支持新式头文件名称的话(见条款E49),就用#include<new.h>。
花几分钟回头想想 placement new,我们便能了解 new operator 和 operator new之间的关系,两个术语虽然表面上令人迷惑,概念上却十分直接易懂。
- 如果你希望将对象产生于heap,请使用 new operator。它不但分配内存而且为该对象调用一个constructor。
- 如果你只是打算分配内存,请调用 operator new,那就没有任何constructor会被调用。
- 如果你打算在heap objects 产生时自己决定内存分配方式,请写一个自己的 operator new,并使用 new operator,它将会自动调用你所写的operator new。
- 如果你打算在已分配(并拥有指针)的内存中构造对象,请使用placement new(若想更深入地了解new和delete,请见条款E7及发表于C/C++Users Journal, April1998的文章《Counting Objects in C++》)
删除(Deletion)与内存释放(Deallocation)
为了避免resource leaks(资源泄漏),每一个动态分配行为都必须匹配一个相应但相反的释放动作。函数operator delete 对于内建的delete operator,就好像operator new对于new operator 一样。当你写出这样的代码:
string *ps;
//。。。
delete ps;// 使用 delete operator。
你的编译器必须产生怎样的代码?
它必须既能够析构 ps所指对象,又能够释放被该对象占用的内存。
内存释放动作是由函数operator delete 执行,通常声明如下:
void operator delete(void *memoryToBeDeallocated);
因此,下面这个动作:
delete ps;
会造成编译器产生近似这样的代码:
ps->~string();// 调用对象的dtoroperator。operator delete (ps);//释放对象所占用的内存。
这里呈现的一个暗示就是,如果你只打算处理原始的、未设初值的内存,应该完全回避new operator 和 delete operators,改调用operator new取得内存并以operator delete 归还给系统:
void *buffer=operator new(50*sizeof(char));//分配足够的内存,放置50个 chars;没有调用任何ctorsoperator delete(buffer);//释放内存,没有调用任何dtors。
这组行为在C++中相当于调用malloc和free。
如果你使用placement new,在某内存块中产生对象,你应该避免对那块内存使用delete opcrator.因为 delete operator会调用 operator delete来释放内存,但是该内存内含的对象最初并非是由 operator new分配得来的。
毕竟placemen new只是返回它所接收的指针而已,谁知道那个指针从哪里来呢?所以为了抵消该对象的constructor的影响,你应该直接调用该对象的destructor:
//以下函数用来分配及释放 shared memory 中的内存。
void* mallocshared(size_t size);
void freeShared(void *memory);void*sharedMemory=mallocShared(sizeof(Widget));//和先前相同,运用Widget*pw=constructWidgetInBuffer(sharedMemory, 10); // placement new。
//..
delete pw;//无定义!因为sharedMemory 来自mallocshared,不是来自 operator new。pw->~Widget();
//可!析构 pw 所指的widget 对象,
//但并未释放widget占用的内存。freeShared(pw);
//可!释放 pw所指的内存,
//不调用任何 destructor。
如此例所示,如果交给placement new 的原始内存(raw memory)本身是动态分配而得(通过某种非传统做法),那么你最终还是得释放那块内存,以免遭受内存泄漏(memory leak)之苦(请参考文章《Counting Objects inC++》之中的方块内容,其中对所谓的“placement delete”有些介绍)。
数组(Arrays)
目前为止一切都好,但我们还有更远的路要走。截至目前我们考虑的每件事情都只在单一对象身上打转。面对数组怎么办?下面会发生什么事情;
string *ps= new string[10);//分配一个对象数组
上述使用的new 仍然是那个new operator,但由于诞生的是数组,所以new operator的行为与先前产生单一对象的情况略有不同。
是的,内存不再以operator new分配,而是由其“数组版”兄弟,一个名为operator new[]的函数负责分配(通常被称为“aray new”)。
和operator new一样,operator new[]也可以被重载。这使你得夺取数组的内存分配权,就像你可以控制单一对象的内存分配一样(不过条款E8对此有些警告)。
operatornew[]是相当晚的时候才加入C++的一个特性,所以你的编译器或许尚未支持它。
如果是这样,全局operator new会被用来为每个数组分配内存一不论数组中的对象类型是什么。
在这样的编译器下定制“数组内存分配行为”很困难,因为你得改写全局版的 operator new才行。这可不是件容易的工作。
默认情况下全局版的operator new负责程序中所有的动态内存分配,所以其行为的任何改变都可能带来剧烈而普遍的影响。此外,全局版本的operator new,其正规形式的型构(eignature)(我的意思是,只有唯一size_t参数的那个,见条款E9)只有一个,所以如果你决定声称它为你所拥有,你的软件便立刻不容于任何做了相同决定的程序库(见条款 27)。
多方考虑之下,如果你面对的是尚未支持 。perator new[]的编译器,定制“数组内存管理行为”往往不是个理想的决定。
“数组版”与“单一对象版”的newoperator的第二个不同是,它所调用的constructor数量。数组版new operator 必须针对数组中的每个对象调用一个constructor:
string *ps =//调用operator new[]以分配足够容纳new string[10];//10个 string 对象的内存,然后
//针对每个元素调用 string default ctor。
同样道理,当delete operator 被用于数组,它会针对数组中的每个元素调用其 destructor,然后再调用 operator delete[]释放内存:
delete [] ps;
//为数组中的每个元素调用 string dtor.
然后调用 operator delete[]以释放内存。
就好像你可以取代或重载 operator delete一样,你也可以取代或重载operator delete[]。不过两者的重载有着相同的限制。请你找一本好的C++教程,查阅其细节。说到好的C++教程,本书p285列有我的一份推荐名单。
现在,你有了完整的知识。
new operator 和delete operator 都是内建操作符,无法为你所控制,但是它们所调用的内存分配/释放函数则不然。
当你想要定制new operator 和 delete operator的行为,记住,你其实无法真正办到。你可以修改它们完成任务的方式,至于它们的任务,已经被语言规范固定死了。
相关文章:
条款8:了解各种不同意义的new和delete
有时候我们觉得,C的术语仿佛是要故意让人难以理解似的。 这里就有一个例子:请说明new operator 和operator new 之间的差异(译注:本书所说的new operator,即某些C教程如C Primer 所谓的new expression) 当你写出这样…...

windows 搭建 go开发环境
go语言(或 Golang)是Google开发的开源编程语言,诞生于2006年1月2日下午15点4分5秒,于2009年11月开源,2012年发布go稳定版。Go语言在多核并发上拥有原生的设计优势,Go语言从底层原生支持并发,无须…...

Android 布局中@NULL的使用和代码实现方式详解
文章目录 1、使用场景2、示例代码实现2.1、移除背景2.2 、移除文本2.3、移除布局宽度或高度2.4、移除提示文本2.5、移除图像资源 3、综合示例3.1、布局文件 activity_main.xml3.2、主活动文件 MainActivity.java3.4、资源文件3.5、运行结果 4、优点5、缺点6、综合分析6.1、适用…...

服务器数据恢复—同友存储raid5阵列上层虚拟机数据恢复案例
服务器数据恢复环境: 某市教育局同友存储,存储中有一组由数块磁盘组建的raid5阵列,存储空间划分若干lun。每个lun中有若干台虚拟机,其中有数台linux操作系统的虚拟机为重要数据。 存储结构: 服务器故障: r…...
我得近况说明
最近转入了Django开发工作,所以主要方向在Python开发。...
C语言——在头⽂件中#if、_STDC_等字⾏起什么作⽤?
一、问题 通常,⼀些程序员都不会去研究头⽂件中的内容是什么含义,总觉得乱乱的,有很多 #if、_STDC_、#line 等字符,那么这些字符都各代表什么呢,在头⽂件中又起到什么作⽤呢? 二、解答 在头⽂件中存在类似…...

解密MySQL中的临时表:探究临时表的神奇用途
欢迎来到我的博客,代码的世界里,每一行都是一个故事 解密MySQL中的临时表:探究临时表的神奇用途 前言临时表的定义与分类创建与使用临时表临时表的操作与管理优化与性能提升注意事项与最佳实践 前言 在数据库管理中,临时表是一个…...

Go 语言简介 -- 高效、简洁与现代化编程的完美结合
在现代软件开发领域,选择合适的编程语言对于项目的成功至关重要。Go 语言(又称 Golang )自 2009 年由Google发布以来,以其简洁的语法、高效的并发模型以及强大的性能,迅速成为开发者们的新宠。Go语言不仅融合了传统编译…...

绝缘鞋计量校准周期多长时间合适?校验检测方法是什么?
绝缘鞋的计量校准,通常是应用在电学相关领域,因此也是属于计量校准机构中的电学室管辖的范围,而绝缘鞋为了安全防护,也是采用了绝缘材料,其标准要求耐电压至少在15KV以下,可应用于工频(50到60F&…...
python-13(案例讲解)
目录 抓取链家前十页的数据 计算均价和总价 计算的类型(整租,合租) 计算的房型 抓取boss直聘前十页的数据 抓取boss直聘前十页的数据 将获取数据本地序列化 计算每个区的需求个数与均价 抓取链家前十页的数据 链家网址:长…...

【深度学习】最强算法之:人工神经网络(ANN)
人工神经网络ANN 1、引言2、人工神经网络(ANN)2.1 定义2.1.1 定义2.1.2 应用场景 2.2 核心原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝:鱼哥,看新闻没? 小鱼:新闻天天看,啥事大惊小怪的。 小屌…...
Unity vscode在mac上的编译环境设置
在settings.json文件中配置以下信息。 settings.json路径一般在/Users/xxx/Library/Application Support/Code/User/settings.json {"omnisharp.useGlobalMono": "always","editor.fontLigatures": false,"omnisharp.useModernNet": …...
【Java】在高并发场景下,保证 Redis 缓存一致性的几种方案
在高并发场景下,保证 Redis 缓存一致性是一个常见的挑战。以下是几种常见的解决方案及其优缺点,以及相应的代码示例。 1. Cache Aside Pattern (旁路缓存模式) 原理 读取数据时,先读缓存,如果缓存没有命中,再从数据…...

GaussDB数据库的备份与恢复
1.逻辑备份-gs_dump gs_dump是一款用于导出数据库相关信息的工具,支持导出完整一致的数据库对象(数据库、模式、表、视图等)数据,同时不影响用户对数据库的正常访问。 备份sql语句 gs_dump是openGauss用于导出数据库相关信息的工…...

03-02-Vue组件之间的传值
前言 我们接着上一篇文章 03-01-Vue组件的定义和注册 来讲。 下一篇文章 04-Vue:ref获取页面节点–很简单 父组件向子组件传值 我们可以这样理解:Vue实例就是一个父组件,而我们自定义的组件(包括全局组件、私有组件)…...

昂达固态硬盘数据恢复方法:全面解析与操作指南
在数字化时代,数据已经成为我们生活和工作中不可或缺的一部分。而固态硬盘(SSD)由于其读写速度快、抗震性强等优点,慢慢取代了传统的机械硬盘,成为我们存储数据的主要选择。然而,即便再先进的存储设备&…...

C++的红黑树
目录 基本概念 插入结点的颜色 判断性质是否破坏 调整方式 u为g的右孩子 u存在且为红 u存在且为黑 u不存在 结论 红黑树结点定义 代码实现 基本概念 1、红黑树是一种特殊的二叉搜索树,每个结点会增加一个存储位表示结点的颜色(红或黑&#x…...
Keras深度学习框架第二十九讲:在自定义训练循环中应用KerasTuner超参数优化
1、简介 在KerasTuner中,HyperModel类提供了一种方便的方式来在可重用对象中定义搜索空间。你可以通过重写HyperModel.build()方法来定义和进行模型的超参数调优。为了对训练过程进行超参数调优(例如,通过选择适当的批处理大小、训练轮数或数…...
手机App收集个人信息,用户是否有权拒绝?
其实过度收集个人信息这件事,在APP上随处可见,泛滥成灾。 前两天有个不疼不痒的小软件“小鸡词典”,因为收集个人信息受到了处罚。 小鸡词典因划分为工具类APP过度收集隐私(手机号、地理位置定位)、不同意政策不能用…...

云下到云上,丽迅物流如何实现数据库降本50% | OceanBase案例
在2024年3月20日的首场OceanBase数据库城市行活动中,专注于物流及供应链解决方案的丽迅物流的架构师阳磊,围绕“OB Cloud在丽迅物流的实践”这一主题,进行了精彩的演讲。本文为此次演讲的内容回顾。 在丽迅物流(Lesoon Logistics…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...