当前位置: 首页 > news >正文

条款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那样,不能被改变意义,总是做相同的事情。

它的动作分为两方面。

  1. 第一,它分配足够的内存,用来放督某类型的对象。以上例而言,它分配足够放置一个string 对象的内存。
  2. 第二,它调用一个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之间的关系,两个术语虽然表面上令人迷惑,概念上却十分直接易懂。

  1. 如果你希望将对象产生于heap,请使用 new operator。它不但分配内存而且为该对象调用一个constructor。
  2. 如果你只是打算分配内存,请调用 operator new,那就没有任何constructor会被调用。
  3. 如果你打算在heap objects 产生时自己决定内存分配方式,请写一个自己的 operator new,并使用 new operator,它将会自动调用你所写的operator new。
  4. 如果你打算在已分配(并拥有指针)的内存中构造对象,请使用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

有时候我们觉得&#xff0c;C的术语仿佛是要故意让人难以理解似的。 这里就有一个例子&#xff1a;请说明new operator 和operator new 之间的差异&#xff08;译注&#xff1a;本书所说的new operator&#xff0c;即某些C教程如C Primer 所谓的new expression) 当你写出这样…...

windows 搭建 go开发环境

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

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阵列上层虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 某市教育局同友存储&#xff0c;存储中有一组由数块磁盘组建的raid5阵列&#xff0c;存储空间划分若干lun。每个lun中有若干台虚拟机&#xff0c;其中有数台linux操作系统的虚拟机为重要数据。 存储结构&#xff1a; 服务器故障&#xff1a; r…...

我得近况说明

最近转入了Django开发工作&#xff0c;所以主要方向在Python开发。...

C语言——在头⽂件中#if、_STDC_等字⾏起什么作⽤?

一、问题 通常&#xff0c;⼀些程序员都不会去研究头⽂件中的内容是什么含义&#xff0c;总觉得乱乱的&#xff0c;有很多 #if、_STDC_、#line 等字符&#xff0c;那么这些字符都各代表什么呢&#xff0c;在头⽂件中又起到什么作⽤呢&#xff1f; 二、解答 在头⽂件中存在类似…...

解密MySQL中的临时表:探究临时表的神奇用途

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

Go 语言简介 -- 高效、简洁与现代化编程的完美结合

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

绝缘鞋计量校准周期多长时间合适?校验检测方法是什么?

绝缘鞋的计量校准&#xff0c;通常是应用在电学相关领域&#xff0c;因此也是属于计量校准机构中的电学室管辖的范围&#xff0c;而绝缘鞋为了安全防护&#xff0c;也是采用了绝缘材料&#xff0c;其标准要求耐电压至少在15KV以下&#xff0c;可应用于工频&#xff08;50到60F&…...

python-13(案例讲解)

目录 抓取链家前十页的数据 计算均价和总价 计算的类型&#xff08;整租&#xff0c;合租&#xff09; 计算的房型 抓取boss直聘前十页的数据 抓取boss直聘前十页的数据 将获取数据本地序列化 计算每个区的需求个数与均价 抓取链家前十页的数据 链家网址&#xff1a;长…...

【深度学习】最强算法之:人工神经网络(ANN)

人工神经网络ANN 1、引言2、人工神经网络(ANN)2.1 定义2.1.1 定义2.1.2 应用场景 2.2 核心原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;看新闻没&#xff1f; 小鱼&#xff1a;新闻天天看&#xff0c;啥事大惊小怪的。 小屌…...

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 缓存一致性的几种方案

在高并发场景下&#xff0c;保证 Redis 缓存一致性是一个常见的挑战。以下是几种常见的解决方案及其优缺点&#xff0c;以及相应的代码示例。 1. Cache Aside Pattern (旁路缓存模式) 原理 读取数据时&#xff0c;先读缓存&#xff0c;如果缓存没有命中&#xff0c;再从数据…...

GaussDB数据库的备份与恢复

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

03-02-Vue组件之间的传值

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

昂达固态硬盘数据恢复方法:全面解析与操作指南

在数字化时代&#xff0c;数据已经成为我们生活和工作中不可或缺的一部分。而固态硬盘&#xff08;SSD&#xff09;由于其读写速度快、抗震性强等优点&#xff0c;慢慢取代了传统的机械硬盘&#xff0c;成为我们存储数据的主要选择。然而&#xff0c;即便再先进的存储设备&…...

C++的红黑树

目录 基本概念 插入结点的颜色 判断性质是否破坏 调整方式 u为g的右孩子 u存在且为红 u存在且为黑 u不存在 结论 红黑树结点定义 代码实现 基本概念 1、红黑树是一种特殊的二叉搜索树&#xff0c;每个结点会增加一个存储位表示结点的颜色&#xff08;红或黑&#x…...

Keras深度学习框架第二十九讲:在自定义训练循环中应用KerasTuner超参数优化

1、简介 在KerasTuner中&#xff0c;HyperModel类提供了一种方便的方式来在可重用对象中定义搜索空间。你可以通过重写HyperModel.build()方法来定义和进行模型的超参数调优。为了对训练过程进行超参数调优&#xff08;例如&#xff0c;通过选择适当的批处理大小、训练轮数或数…...

手机App收集个人信息,用户是否有权拒绝?

其实过度收集个人信息这件事&#xff0c;在APP上随处可见&#xff0c;泛滥成灾。 前两天有个不疼不痒的小软件“小鸡词典”&#xff0c;因为收集个人信息受到了处罚。 小鸡词典因划分为工具类APP过度收集隐私&#xff08;手机号、地理位置定位&#xff09;、不同意政策不能用…...

云下到云上,丽迅物流如何实现数据库降本50% | OceanBase案例

在2024年3月20日的首场OceanBase数据库城市行活动中&#xff0c;专注于物流及供应链解决方案的丽迅物流的架构师阳磊&#xff0c;围绕“OB Cloud在丽迅物流的实践”这一主题&#xff0c;进行了精彩的演讲。本文为此次演讲的内容回顾。 在丽迅物流&#xff08;Lesoon Logistics…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...

Spring AOP代理对象生成原理

代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】&#xff0c;这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...