C++语言一直被认为是复杂编程语言中的杰出代表之一,不仅仅是因为其繁缛的语法规则,还因为其晦涩的术语。下面要讲的就是你的老熟人—new:

它是一个内存管理的操作符,能够从堆中划分一块区域,自动调用构造函数,动态地创建某种特定类型的数据,最后返回该区域的指针。该数据使用完后,应调用delete运算符,释放动态申请的这块内存。

如果这就是你对new的所有认识,那么我不得不说,你依旧被new的和善外表所蒙蔽着。看似简单的new其实有着三种不同的外衣。

是的,你没有看错,也不用感到惊奇,一个简单的new确实有三种不同的形态,它扮演着三种不同的角色,如下所示:

new operator
operator new
placement new

下面的代码片段展示的是我们印象中熟悉的那个new:

string *pStr = new string("Memory Management");
int *pInt = new int();

这里所使用的new是它的第一种形态new operator。它与sizeof有几分类似,它是语言内建的,不能重载,也不能改变其行为,无论何时何地它所做的有且只有以下三件事,如下图所示。

所以当写出“string *pStr = new string("Memory Management");”代码时,它其实做的就是以下几件事:

//为string对象分配raw内存
void *memory = operator new( sizeof(string) );
//调用构造函数,初始化内存中的对象
call string::string()on memory;
//获得对象指针
string *pStr = static_cast<string*>(memory);
当然,对于内置类型,第二步是被忽略的,即:
//为int分配raw内存
void *memory = operator new( sizeof(int) );
//获得对象指针
int *pInt = static_cast<int*>(memory);

其实new operator背后还藏着一个秘密,即它在执行过程中,与其余的两种形态都发生了密切的关系:第一步的内存申请是通过operator new完成的;而在第二步中,关于调用什么构造函数,则由new的另外一种形态placement new来决定的。

对于new的第二种形态—内存申请中所调用的operator new,它只是一个长着“明星脸”的普通运算符,具有和加减乘除操作符一样的地位,因此它也是可以重载的。

operator new在默认情况下首先会调用分配内存的代码,尝试从堆上得到一段空间,同时它对事情的结果做了最充分的准备:如果成功则直接返回;否则,就转而去调用一个new_hander,然后继续重复前面过程,直到异常抛出为止。所以如果operator new要返回,必须满足以下条件之一:

内存成功分配。

抛出bad_alloc异常。

通常,operator new函数通过以下方式进行声明:

void* operator new(size_t size); 

注意,这个函数的返回值类型是void*,因为这个函数返回的是一个未经处理的指针,是一块未初始化的内存,它像极了C库中的malloc函数。如果你对这个过程不满意,那么可以通过重载operator new来进行必要的干预。例如:

class A
{
public:
A(int a);
~A();
void* operator new(size_t size);
...
};
void* A::operator new(size_t size)
{
cout<<"Our operator new...");
return ::operator new(size);
}

这里的operator new调用了全局的new来进行内存分配(::operator new(size))。当然这里的全局new也是可以重载的,但是在全局空间中重载void * operator new(size_t size)函数将会改变所有默认的operator new的行为方式,所以必须十二分的注意。还有一点需要注意的是,正像new与delete一一对应一样,operator new和operator delete也是一一对应的;如果重载了operator new,那么也得重载对应的operator delete。

最后,要介绍的是new的第三种形态—placement new:定位new运算符。正如前面所说的那样,placement new是用来实现定位构造的,可以通过它来选择合适的构造函数。虽然通常情况下,构造函数是由编译器自动调用的,但是不排除你有时确实想直接手动调用,比如对未初始化的内存进行处理,获取想要的对象,此时就得求助于一个叫做placement new的特殊的operator new了:

#include <new>
#include "ClassA.h"
int main()
{
void *s = operator new(sizeof(A));
A* p = (A*)s;
new(p) A(); //p->A::A(2011);
... // processing code
return ;
}

placement new是标准C++库的一部分,被声明在了头文件中,所以只有包含了这个文件,我们才能使用它。它在文件中的函数定义很简单,如下所示:

#ifndef __PLACEMENT_NEW_INLINE
#define __PLACEMENT_NEW_INLINE
inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
} inline void __CRTDECL operator delete(void *, void *) _THROW0()
{ // delete if placement new fails
}
#endif /* __PLACEMENT_NEW_INLINE */

这就是placement new需要完成的事。细心的你可能会发现,placement new的定义与operator new声明之间的区别:placement new的定义多一个void*参数。使用它有一个前提,就是已经获得了指向内存的指针,因为只有这样我们才知道该把placement new初始化完成的对象放在哪里。

在使用placement new的过程中,我们看到的却是"new(p) A(2011)"这样奇怪的调用形式,它在特定的内存地址上用特定的构造函数实现了构造一个对象的功能,A(2011)就是对构造函数A(int a)的显式调用。当然,如果显式地调用placement new,那么也得本着负责任的态度显式地调用与之对应的placement delete:p->~A();。这部分工作本来可以由编译器独自完成的:在使用new operator的时候,编译器会自动生成调用placement new的代码,相应的,在调用delete operator时同样会生成调用析构函数的代码。所以,除非特别必要,不要直接使用placement new。但是要清楚,它是new operator的一个不可或缺的步骤。当默认的new operator对内存的管理不能满足我们的需要,希望自己手动管理内存时,placement new就变得有用了。就像STL中的allocator一样,它借助placement new来实现更灵活有效的内存管理。下面举例说明:

#include  "stdafx.h"
#include "new" class Student
{
public:
int _NO;
int _age;
int _sex;
public:
Student( int NO, int age, int sex)
{
_NO=NO;
_age=age;
_sex=sex;
}
}; int _tmain(int argc, _TCHAR* argv[])
{
char buffer[];
Student* pStudent = NULL;
pStudent =new (buffer) Student(,,);
return ;
}

最后,总结一下:

如果是在堆上建立对象,那么应该使用 new operator,它会为你提供最为周全的服务。

如果仅仅是分配内存,那么应该调用operator new,但初始化不在它的工作职责之内。如果你对默认的内存分配过程不满意,想单独定制,重载operator new 是不二选择。

如果想在一块已经获得的内存里建立一个对象,那就应该用placement new。但是通常情况下不建议使用,除非是在某些对时间要求非常高的应用中,因为相对于其他两个步骤,选择合适的构造函数完成对象初始化是一个时间相对较长的过程。

请记住:

不要自信地认为自己对new很熟悉,要正确区分new所具有的三种不同形态,并能在合适的情形下选择合适的形态,以满足特定需求。

原文链接:new的三种形态

其它链接:

1、c++ placement new操作符的使用技巧

2、C++中在指定的内存位置,调用构造函数

new的三种形态的更多相关文章

  1. QTreeView/QTableView中利用QStandardItem实现复选框三种形态变化

    https://www.techieliang.com/2017/12/729/ 原文地址 using_checkbox_item.h /** * @file using_checkbox_item. ...

  2. 【淡墨Unity3D Shader计划】五 圣诞用品: Unity在Shader三种形式的控制&amp;混合操作编译

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/42060963 作者:毛星云(浅墨)  ...

  3. jQuery插件开发的五种形态[转]

    这篇文章主要介绍了jQuery插件开发的五种形态小结,具体的内容就是解决javascript插件的8种特征,非常的详细. 关于jQuery插件的开发自己也做了少许研究,自己也写过多个插件,在自己的团队 ...

  4. 手机三种SIM卡 你所不知道的剪卡“秘密”

    SIM卡物理尺寸的发展是逐渐轻薄化,尺寸逐渐缩小的一个过程,最早手机中的卡都是2FF,2003年国际标准提出3FF,当前很多终端都使用这种形态的卡,4FF在2011年的国际标准会议中提出,2012年纳 ...

  5. N个节点的二叉树有多少种形态(卡特兰数)

    N个节点的二叉树有多少种形态   这是一道阿里的面试题.其实算不上新鲜,但是我之前没关注过,如今碰到了,就顺便探讨下这个问题吧:) 拿到这个题,首先想到的是直接写出表达式肯定不行,所以有必要从递推入手 ...

  6. 2、shader基本语法、变量类型、shader的三种形式、subshader、fallback、Pass LOD、tags

    新建一个shader,名为MyShader1内容如下: 1._MainTex 为变量名 2.“Base (RGB)”表示在unity编辑面板中显示的名字,可以定义为中文 3.2D 表示变量的类型 4. ...

  7. 盛大游戏技术总监徐峥:Unity引擎使用的三种方式

    在5月13日Unite 2017 案例分享专场上,盛大游戏技术总监徐峥分享了使用Unity引擎的三种方式,以下为详细内容: 大家好,我先简单介绍一下我自己,我是盛大游戏的技术总监徐峥.我今天想分享的主 ...

  8. 不吹不黑,今天我们来聊一聊 Kubernetes 落地的三种方式

    作者 | 王国梁  Kubernetes 社区成员与项目维护者原文标题<Kubernetes 应用之道:让 Kubernetes落地的"三板斧">,首发于知乎专栏:进击 ...

  9. 简谈百度坐标反转至WGS84的三种思路

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 基于百度地图进行数据展示是目前项目中常见场景,但是因为百度地图 ...

随机推荐

  1. python学习之老男孩python全栈第九期_day016知识点总结

    '''数据类型:intbool... 数据结构:dict (python独有的)listtuple (pytho独有的)setstr''' # reverse() 反转l = [1,2,3,4,5]l ...

  2. Ajax 的几种方法应用

    一,js实现ajax异步请求,简单例子 try.jsp <%@ page language="java" import="java.util.*" pag ...

  3. Git 学习之git 起步(一)

    起步 本章介绍开始使用 Git 前的相关知识.我们会先了解一些版本控制工具的历史背景,然后试着让 Git 在你的系统上跑起来,直到最后配置好,可以正常开始开发工作.读完本章,你就会明白为什么 Git ...

  4. LOJ#6032. 「雅礼集训 2017 Day2」水箱

    传送门 首先可以有一个平方复杂度的 \(DP\) 设 \(f_{i,j}\) 表示前面 \(i\) 个小格,高度为 \(j\) 的最大答案 令 \(h_i\) 表示隔板 \(i\) 的高度 当 \(j ...

  5. Google JavaScript样式指南

    Google JavaScript样式指南   目录 1简介 1.1术语说明 1.2指南说明 2源文件基础知识 2.1文件名 2.2文件编码:UTF-8 2.3特殊字符 3源文件结构 3.1许可或版权 ...

  6. WPF ListView 分组 Grouping

    在Resource里定义数据源和分组字段: <CollectionViewSource x:Key="listData" Source="{Binding Cate ...

  7. SwipeRefreshLayout的高度测量

    感谢此作者的分享 http://www.cnblogs.com/linjzong/p/5221604.html 若SwipeRefreshLayout的子布局为一个线性布局LinearLayout, ...

  8. flutter 生命周期

    前言:生命周期是一个组件加载到卸载的整个周期,熟悉生命周期可以让我们在合适的时机做该做的事情, flutter中的State生命周期和android以及React Native的生命周期类似. 先看一 ...

  9. join() 方法详解及应用场景

    总结:join方法的功能就是使异步执行的线程变成同步执行.也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用jo ...

  10. SpringBoot 之配置server 信息

    一.修改端口号 spring-boot 默认的端口号是8080,如需修改. 1.新建一个src/main/resources 文件夹 2.在这个文件夹下新建一个application.properti ...