C++ 中 new 操作符内幕:new operator、operator new、placement new

一、new 操作符(new operator)

人们有时好像喜欢有意使C++语言的术语难以理解。比方说new操作符(new operator)和operator new的差别。

当你写这种代码:

string *ps = new string("Memory Management");

你使用的new是new操作符
这个操作符就象sizeof一样是语言内置的。你不能改变它的含义,它的功能总是一样的。它要完毕的功能分成两部分。第一部分是分配足够的内存以便容纳所需类型的对象。

第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以不论什么方式改变它的行为。
(总结就是,new操作符做两件事,分配内存+调用构造函数初始化。你不能改变它的行为。)

二、operator new

你所能改变的是怎样为对象分配内存

new操作符调用一个函数来完毕必需的内存分配,你可以重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。
函数operator new 通常这样声明:

void * operator new(size_t size);

返回值类型是void*,由于这个函数返回一个未经处理(raw)的指针。未初始化的内存。(假设你喜欢。你能写一种operator new函数,在返回一个指针之前可以初始化内存以存储一些数值,可是一般不这么做。)參数size_t确定分配多少内存。

你能添加额外的參数重载函数operator new,可是第一个參数类型必须是size_t。如: new(__FILE__, __LINE__)

(有关operator new很多其它的信息參见Effective C++ 条款8至条款10。)

你一般不会直接调用operator new,可是一旦这么做。你能够象调用其他函数一样调用它:

void *rawMemory = operator new(sizeof(string));

操作符operator new将返回一个指针,指向一块足够容纳一个string类型对象的内存。
就象malloc一样,operator new的职责仅仅是分配内存。

它对构造函数一无所知。operator new所了解的是内存分配。把operator new 返回的未经处理的指针传递给一个对象是new操作符的工作。当你的编译器遇见这种语句:

string *ps = new string("Memory Management");

它生成的代码或多或少与以下的代码相似(很多其它的细节见Effective C++条款8和条款10。还有我的文章Counting object里的凝视。):

void *memory = operator new(sizeof(string)); // 得到未经处理的内存,为String对象
call string::string("Memory Management") on *memory; // 内存中的对象
string *ps = static_cast<string*>(memory); // 使ps指针指向新的对象

注意第二步包括了构造函数的调用,你做为一个程序猿被禁止这样去做。你的编译器则没有这个约束,它能够做它想做的一切。
因此假设你想建立一个堆对象就必须用new操作符。不能直接调用构造函数来初始化对象。(总结:operator new是用来分配内存的函数,为new操作符调用。能够被重载(有限制)

三、placement new

有时你确实想直接调用构造函数。在一个已存在的对象上调用构造函数是没有意义的,由于构造函数用来初始化对象。而一个对象只能在给它初值时被初始化一次。

可是有时你有一些已经被分配可是尚未处理的的(raw)内存,你须要在这些内存中构造一个对象。你能够使用一个特殊的operator new ,它被称为placement new。

以下的样例是placement new怎样使用,考虑一下:

class Widget {public:Widget(int widgetSize);...
};Widget * constructWidgetInBuffer(void *buffer,int widgetSize)
{return new (buffer) Widget(widgetSize);
}

这个函数返回一个指针。指向一个Widget对象,对象在转递给函数的buffer里分配。
当程序使用共享内存或memory-mapped I/O时这个函数可能实用,由于在这样程序里对象必须被放置在一个确定地址上或一块被例程分配的内存里。(參见条款4,一个怎样使用placement new的一个不同样例。)

在constructWidgetInBuffer里面。返回的表达式是: new (buffer) Widget(widgetSize)
这初看上去有些陌生,可是它是new操作符的一个使用方法,须要使用一个额外的变量(buffer)。当new操作符隐含调用operator new函数时。把这个变量传递给它。被调用的operator new函数除了带有强制的參数size_t外,还必须接受void*指针參数。指向构造对象占用的内存空间。这个operator new就是placement new,它看上去象这样:

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

这可能比你期望的要简单,可是这就是placement new须要做的事情。毕竟operator new的目的是为对象分配内存然后返回指向该内存的指针。在使用placement new的情况下,调用者已经获得了指向内存的指针。由于调用者知道对象应该放在哪里。placement new必须做的就是返回转递给它的指针。(没实用的(可是强制的)參数size_t没有名字,以防止编译器发出警告说它没有被使用。见条款6。
) placement new是标准C++库的一部分。为了使用placement new。你必须使用语句#include <new>(或者假设你的编译器还不支持这新风格的头文件名称)。

(总结:placement new是一种特殊的operator new,作用于一块已分配但未处理或未初始化的raw内存)

四、小结

让我们从placement new回来片刻,看看new操作符(new operator)与operator new的关系,(new操作符调用operator new)

  • 你想在堆上建立一个对象,应该用new操作符。它既分配内存又为对象调用构造函数。
  • 假设你只想分配内存,就应该调用operator new函数;它不会调用构造函数。
  • 假设你想定制自己的在堆对象被建立时的内存分配过程,你应该写你自己的operator new函数。然后使用new操作符,new操作符会调用你定制的operator new。
  • 假设你想在一块已经获得指针的内存里建立一个对象。应该用placement new。

五、Deletion and Memory Deallocation

为了避免内存泄漏,每一个动态内存分配必须与一个等同相反的deallocation相应。

1.函数operator delete与delete操作符的关系与operator new与new操作符的关系一样。当你看到这些代码:

string *ps;
...
delete ps; // 使用delete 操作符

你的编译器会生成代码来析构对象并释放对象占有的内存。
Operator delete用来释放内存。它被这样声明:

void operator delete(void *memoryToBeDeallocated);

因此, delete ps; 导致编译器生成类似于这种代码:

ps->~string(); // call the object's dtor
operator delete(ps); // deallocate the memory the object occupied

这有一个隐含的意思是假设你仅仅想处理未被初始化的内存,你应该绕过new和delete操作符,而调用operator new 获得内存和operator delete释放内存给系统:

void *buffer = operator new(50*sizeof(char)); // 分配足够的内存以容纳50个char//没有调用构造函数...
operator delete(buffer); // 释放内存// 没有调用析构函数

这与在C中调用malloc和free等同。
2.placement new建立的对象怎样释放?
假设你用placement new在内存中建立对象,你应该避免在该内存中用delete操作符。

由于delete操作符调用operator delete来释放内存,可是包括对象的内存最初不是被operator new分配的。placement new仅仅是返回转递给它的指针。谁知道这个指针来自何方?而你应该显式调用对象的析构函数来解除构造函数的影响:

// 在共享内存中分配和释放内存的函数 void * mallocShared(size_t size);void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = // 如上所看到的,
constructWidgetInBuffer(sharedMemory, 10); // 使用// placement new ...
delete pw; // 结果不确定! 共享内存来自
// mallocShared, 而不是operator newpw->~Widget(); // 正确。 析构 pw指向的Widget,// 可是没有释放
//包括Widget的内存freeShared(pw); // 正确。 释放pw指向的共享内存// 可是没有调用析构函数

如上例所看到的,假设传递给placement new的raw内存是自己动态分配的(通过一些不经常使用的方法),假设你希望避免内存泄漏,你必须释放它。(參见我的文章Counting objects里面关于placement delete的凝视。)

六、数组

到眼下为止一切顺利。可是还得接着走。

到眼下为止我们所測试的都是一次建立一个对象。

如何分配数组?会发生什么?

string *ps = new string[10]; // allocate an array of objects

被使用的new仍然是new操作符,可是建立数组时new操作符的行为与单个对象建立有少许不同。
第一是内存不再用operator new分配,取代以等同的数组分配函数,叫做operator new[](常常被称为array new)。

它与operator new一样能被重载。

这就同意你控制数组的内存分配。就象你能控制单个对象内存分配一样(可是有一些限制性说明,參见Effective C++ 条款8)。

(operator new[]对于C++来说是一个比較新的东西。所以你的编译器可能不支持它。假设它不支持。不管在数组中的对象类型是什么。全局operator new将被用来给每一个数组分配内存。
在这种编译器下定制数组内存分配是困难的。由于它须要重写全局operator new。这可不是一个能轻易接受的任务。

缺省情况下,全局operator new处理程序中全部的动态内存分配,所以它行为的不论什么改变都将有深入和普遍的影响。并且全局operator new有一个正常的签名(normal signature)(也就单一的參数size_t。參见Effective C++条款9)。所以假设你 决定用自己的方法声明它,你立马使你的程序与其他库不兼容基于这些考虑,在缺乏operator new[]支持的编译器里为数组定制内存管理不是一个合理的设计。)

第二个不同是new操作符调用构造函数的数量。对于数组,在数组里的每个对象的构造函数都必须被调用:

string *ps = new string[10]; // 调用operator new[]为10个string对象分配内存,// 然后对每一个数组元素调用string对象的缺省构造函数。

相同当delete操作符用于数组时,它为每一个数组元素调用析构函数,然后调用operator delete来释放内存。(buxizhizhou530注:这里应该是operator delete[]吧)
就象你能替换或重载operator delete一样,你也替换或重载operator delete[]。

在它们重载的方法上有一些限制。

请參考优秀的C++教材。

(总结:数组时,两个不同点,一时调用operator new[]函数,二是new操作符调用构造函数的数量不同。)

七、总结

new和delete操作符是内置的,其行为不受你的控制。凡是它们调用的内存分配和释放函数则能够控制。当你想定制new和delete操作符的行为时,请记住你不能真的做到这一点。你仅仅能改变它们为完毕它们的功能所採取的方法,而它们所完毕的功能则被语言固定下来。不能改变。(You can modify how they do what they do, but what they do is fixed by the language)
參考:C++中delete, new以及new [], delete[]操作符内幕
本文大部分内容来源于上述博文,可惜这个博文也是转载,没有找到原出处。本文仅是依据上述博文内容,加上自己理解,进行了又一次排版、颜色标注、相关改动、及自己的总结。

假设错误。欢迎交流~

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

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

相关文章

Flask-1-05-CookieSession

接下来我会演示一下设置Cookie 读取Cookie 删除Cookie&#xff0c;以及添加Cookie的原理 接下来我们分别定义3个视图为 set_cookie、get_cookie、del_cookie # coding:utf-8from flask import Flask, make_response, requestapp Flask(__name__)app.route("/set_cookie&q…

18个不可不知的有用潜规则

人情世故是我们日常生活中积累的约定俗成的行为规则&#xff0c;属于社会知识的范畴。这些知识大半来源于与不同人群的社会交际&#xff0c;也来源于社会冲突与社会发展。在有专业知识与技能的情况下&#xff0c;人情世故能够帮助我们个人缓和与其他人之间的紧张度&#xff0c;…

解决跨域问题:No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 PS&#xff1a;如果遇到 这个问题 Request header field Content-Type is not allowed by Access-Control-Allow-Headers&#xff0c;解…

错误代码:0xc000007b 解决思路

三维电子沙盘项目&#xff1a; 现场环境部署时&#xff0c;二维平台和模型在实际部署中遇到了0xc000007b的问题&#xff0c;网上很多说是DX的问题&#xff0c;但并不能解决。 之前在编译OpenDDS时也遇到过类似的问题&#xff0c;是在不同版本的OpenDDS的动态库混用时遇到的&a…

上传本地项目到git

1、到需要上传的文件夹下&#xff0c;打开gitbase 2、git init 3、git add . 4、git commit -m init 5、验证权限 首先在Git Bash中输入:ssh-keygen -t rsa -C "youremailxxx.com" 然后一路回车,这个会在当前用户文件夹下&#xff0c;生成.ssh 文件夹&#xff0c;里边…

vue 设置全局变量、指定请求的 baseurl

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 安装 axios&#xff1a; npm install axios --save-dev 2. 新建一个 Base.vue 文件&#xff0c;书写内容如下&#xff1a; <sc…

解决这九种困扰 可以让你每天精神百倍

晚上睡眠是人体最好的休息方式&#xff0c;人体的很多症状都会在充足的睡眠后得到缓解&#xff0c;良好的睡眠比世界上的任何药都灵&#xff0c;而现代社会&#xff0c;“一觉睡到自然醒”已是很多人可望而不可及的了。我经过多年的仔细观察&#xff0c;发现只要是在早晨醒来后…

基于java的数据结构学习——泛型动态数组的封装

public class Array<E> {private E[] data;private int size;// 构造函数public Array(int Capacity){data (E[])new Object[Capacity];size 0;}// 默认构造函数public Array(){this(10);}// 判断数组是否已满public boolean isFull(){return size data.length;}// 判…

POJ1207-The 3n + 1 problem

http://poj.org/problem?id1207 注意输入两个数的大小&#xff0c;水。。。 #include <stdio.h> int main(void) {int a,b,m,count,i,max;while(scanf("%d%d",&a,&b)!EOF){printf("%d %d ",a,b);if(a>b){ma;ab;bm;}max0;for(ia;i<b;i…

解决:vue.esm.js?efeb:591 [Vue warn]: Do not use built-in or reserved HTML elements as component id: me

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 报错如题&#xff1a; vue.esm.js?efeb:591 [Vue warn]: Do not use built-in or reserved HTML elements as component id: menu …

JetBrains 系列软件汉化包

Android Studio 3.0-3.1 汉化包 CLion 2018.1 汉化包 GoLand 2017.3.2-2018.1 汉化包 IntelliJ IDEA 2017.3-2018.1 汉化包 PhpStorm 2017.3-2018.1 汉化包 PyCharm 2017.3-2018.1 汉化包 RubyMine 2017.3.2-2018.1 汉化包 WebStorm 2017.3-2018.1 汉化包 下载完毕后&#xff…

JAXP进行DOM和SAX解析

1.常用XML的解析方式&#xff1a;DOM和SAX 1&#xff09;DOM思想&#xff1a;将整个XML加载内存中&#xff0c;形成文档对象&#xff0c;所以对XML操作都对内存中文档对象进行。 2&#xff09;SAX思想&#xff1a;一边解析&#xff0c;一边处理&#xff0c;一边释放内存资源---…

VScode 格式化代码快捷键、修改快捷键

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 请看仔细快捷键是&#xff1a; shift alt F // 我是从 eclipse 转的 idea &#xff0c;现在再用 vscode , 一直条件反射的按的 c…

信息学竞赛的常数优化、常见问题、代码风格相关

在查std::ios::sync_with_stdio(false);有关信息时&#xff0c;看到https://blog.csdn.net/qq_33583069/article/details/53086992 这篇博客&#xff0c;对其中一些问题比较感兴趣&#xff0c;整理了下相关资料&#xff1a; isdigit()https://blog.csdn.net/cupidove/article/d…

TCP报文格式详解

TCP协议只定义了一种报文格式 建立、拆除连接、传输数据使用同样的报文 TCP报文格式 TCP报文段首部&#xff08;20个字节&#xff09; 源端口和目的端口&#xff1a;各占2个字节&#xff0c;16比特的端口号加上32比特的IP地址&#xff0c;共同构成相当于传输层服务访问点的地址…

Vue 生命周期中 mounted( ) 和 created( ) 的区别

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、什么是生命周期&#xff1f; 用通俗的语言来说&#xff0c;就是Vue中实例或者组件从创建到消灭中间经过的一系列过程。虽然不太严谨…

科目三考试

马上要考大路了&#xff0c;考科目三考试必须做到什么事啊&#xff1f; 2012-5-30 8:46:23检举  注意事项   1、上车后确认车上电脑信息后&#xff0c;关好车门&#xff0c;系好安全带&#xff0c;打左转向灯起步。   2、直线行驶路段必须使用二档行驶。否则将被判…

基于java的数据结构学习——数组实现的栈以及简单应用

栈 Stack 栈是一种线性结构相比数组&#xff0c;栈对应的操作是数组的子集只能从一端添加元素&#xff0c;也只能从一端取出元素这一端称为栈顶栈是一种后进先出的数据结构 栈的应用 无处不在的Undo操作&#xff08;撤销&#xff09;括号匹配&#xff08;编译器&#xff09;程…

Python 总结题目

题例1 # 打印如下长方形&#xff1a; ************ * * * * ************ # 打印如下长方形&#xff1a; print("*****************") print("* *") print("* *") print("****************…

vue : 引入、安装 jquery 、bootstrap

一、vue安装jquery 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1、新建一个vue工程。 2、在项目文件夹下&#xff0c;使用命令 npm install jquery --save-dev 引入jquery。 np…