条款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的行为,记住,你其实无法真正办到。你可以修改它们完成任务的方式,至于它们的任务,已经被语言规范固定死了。

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

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

相关文章

粒子爱心特效||轻松实现浪漫效果||完整代码

关注微信公众号「ClassmateJie」有完整代码以及更多惊喜等待你的发现。 简介/效果展示 你是否曾经想过&#xff0c;在特殊的日子里给你的爱人一个惊喜&#xff1f;或者在朋友的生日派对上&#xff0c;给他们展示一个充满爱意的特效&#xff1f;今天&#xff0c;我要分享一个我…

VUE3-form表单保存附件与基本信息

element-ui代码 <el-dialog :title"上传附件" v-model"dialogAdds.visible" width"500px" append-to-body> <el-form-item label"唯一标识"> <dict-tag v-if"form.groupId" :options"unique_identifica…

[大师C语言(第十二篇)]C语言堆排序技术详解

引言 堆排序&#xff08;Heap Sort&#xff09;是一种基于比较的排序算法&#xff0c;它利用堆这种数据结构的特点来进行排序。堆是一种近似完全二叉树的结构&#xff0c;并同时满足堆积的性质&#xff1a;即子节点的键值或索引总是小于&#xff08;或者大于&#xff09;它的父…

性能怪兽!香橙派 Kunpeng Pro 开发板深度测评,带你解锁无限可能

性能怪兽&#xff01;香橙派 Kunpeng Pro 开发板深度测评&#xff0c;带你解锁无限可能 文章目录 性能怪兽&#xff01;香橙派 Kunpeng Pro 开发板深度测评&#xff0c;带你解锁无限可能一、背景二、香橙派 Kunpeng Pro 硬件规格概述三、使用准备与系统安装1️⃣、系统安装步骤…

【C++】浅论(cin和cout)的解锁、缓冲区的理解、CC++输入方法汇总和详解

一、cin,cout解锁 1.1&#xff1a;cin,cout解锁以及why 首先cin和cout是在c中为了提供类型安全和易用性设计的&#xff0c;它兼容了c语言的输入和输出&#xff0c;以上几点导致它在性能行&#xff08;读取和输出速度)远不如传统c语言的输入和输出。 在看到一些代码里面&…

Python 脚本化 Git 操作:简单、高效、无压力

前言 如何判定此次测试是否达标&#xff0c;代码覆盖率是衡量的标准之一。前段时间&#xff0c;利用fastapi框架重写了覆盖率统计服务&#xff0c;核心其实就是先获取全量代码覆盖率&#xff0c;然后通过diff操作统计增量代码覆盖率&#xff0c;当然要使用diff操作&#xff0c…

Java中Stack的使用详解

Stack是一种运算受限的线性表&#xff0c;其特点在于仅允许在表的一端&#xff08;即表尾&#xff09;进行插入和删除操作。这一端被称为栈顶&#xff0c;而相对的另一端则称为栈底。向一个栈插入新元素的操作称为进栈或入栈&#xff0c;它将新元素放到栈顶元素的上面&#xff…

从杂乱无章到井井有条——五款笔记软件,重塑工作与生活

记得刚入职场那会&#xff0c;我总是被各种繁杂的信息和任务搞得焦头烂额。会议记录、项目计划、灵感闪现……这些都需要我随时记录和整理。 然而&#xff0c;我的桌面总是堆满了便签纸和草稿本&#xff0c;手机相册里充斥着各种截图和备忘录&#xff0c;每次需要查找资料都像…

【数据结构】红黑树——领略天才的想法

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 目录 二叉搜索树 AVL树 红黑树概述 性质详解 效率对比 旋转操作 元素操作 代码实现 二叉搜索树 【数据结构】二叉搜索树-CSDN博客 AVL树 【数据结构】AVL树——平衡二叉搜索…

深度学习实战-yolox训练ExDark数据集(附全过程代码,超详细教程,无坑!)

跳转:数据集获取以及前期准备工作 本人在深度学习实战-yolov5训练ExDark数据集(附全过程代码,超详细教程,无坑!)的数据基础上实现yolox的训练,所以先跳转到该文章下去获取数据集,再继续接下来操作过程。 一、VOC格式数据集制作 1.前期工作 2.转变成voc格式 在datase…

Latex:newcommand

参考文献&#xff1a; latex中自定义的命令———\newcommand-CSDN博客LaTeX技巧924&#xff1a;详解newcommand的参数和默认值 - LaTeX工作室 (latexstudio.net) 文章目录 (re)newcommand自定义的一些命令 (re)newcommand ”定义命令“ 的定义&#xff1a; \newcommand{<…

[每周一更]-(第98期):PHP版本的升级历程

文章目录 大致历程PHP/FI (PHP 1.0)PHP 2.0PHP 3.0PHP 4.0PHP 5.0PHP 5.3 - 5.6PHP 7.0PHP 7.1 - 7.4PHP 8.0PHP 8.1 - 8.2 参考 PHP&#xff0c;即“超文本预处理器”&#xff08;Hypertext Preprocessor&#xff09;&#xff0c;是广泛应用于web开发的服务器端脚本语言。自19…

什么是独特摆动交易策略?fpmarkets1分钟讲清楚

摆动交易策略想必各位投资者都已经接触过了&#xff0c;但是什么是独特摆动交易策略&#xff1f;各位投资者知道吗&#xff1f;其实很简单&#xff0c;这是一种基于斐波纳契工具的独特摆动交易策略。下面fpmarkets1分钟讲清楚&#xff0c;趋势总会经历调整&#xff0c;而这些调…

【机器学习】Python中的决策树算法探索

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Python中的决策树算法探索引言1. 决策树基础理论1.1 算法概述1.2 构建过程 2. P…

数据集003:猫类识别-12种猫分类数据集 (含数据集下载链接)

数据集简介&#xff1a; 训练集共有2160张猫的图片, 分为12类. train_list.txt是其标注文件 测试集共有240张猫的图片. 不含标注信息. 训练集图像&#xff08;部分&#xff09; 验证集图像&#xff08;部分&#xff09; 标签 部分代码&#xff1a; # 定义训练数据集 class T…

eNSP华为模拟器-DHCP配置

拓扑图 要求 PC1通过DHCP获取192.168.1.1地址PC2和PC3通过DHCP接口地址池方式获取IP地址配置静态路由使其ping通 配置 配置主机名及接口IP地址 # AR1 <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sys AR1 [AR1]int g0/0/0 [AR1-Gigabit…

在leaflet上创建图标

参考之前博客进行创建leaflet地图 添加图标 customIcon L.icon({iconUrl: helicopter0.png,//图片路径放在public中iconSize: [35, 35],iconAnchor: [15, 15],tooltipAnchor: [20, 0],}); let marker L.marker([obj.lat, obj.lon], { icon: customIcon, rotationAngle: 偏转…

去重复记录和排序——kettle开发09

一、去除重复记录 去除重复记录&#xff0c;就是将数据流中的数据进行字段比较&#xff0c;从而去掉重复值的过程。去除重复记录的前提是需要将数据流中的数据进行排序&#xff0c;然后再进行去重操作。 去除重复记录的逻辑是&#xff0c;如下图&#xff0c;我们将需要比较的…

MySQL + MyBatis-Plus 分页数据重复问题

参考文章&#xff1a;java - MySQL MyBatis-Plus 分页数据重复问题 - 个人文章 - SegmentFault 思否

基础使用-SQL-图形化界面工具DataGrip

一、连接mysql &#xff08;1&#xff09;选择加号&#xff0c;再选择添加一个数据源&#xff08;Data Source&#xff09;&#xff0c;然后选择MySQL &#xff08;2&#xff09;接下来就需要去配置MySQL的连接信息&#xff0c;并且去下载它的驱动&#xff0c;安装驱动时可能要…