new的三种形态

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

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

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

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

  1. new operator
  2. operator new
  3. placement new

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

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

这里所使用的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。正如前面所说的那样,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(2011);  //p->A::A(2011);
 ... // processing code
     return 0;
} 

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来实现更灵活有效的内存管理。

最后,总结一下:

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

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

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

请记住:

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

原文链接:new的三种形态

其它链接:C++中在指定的内存位置,调用构造函数

原文地址:https://www.cnblogs.com/rainbow70626/p/8884795.html

时间: 2024-10-06 22:27:18

new的三种形态的相关文章

【浅墨Unity3D Shader编程】之五 圣诞夜篇: Unity中Shader的三种形态对比&amp;混合操作合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处.  文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1222/164.html 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 邮箱: [email protected] QQ交流群:330595914 更多文章尽在:http://www.hpw123.net 本文算是固定功能Shader的最后一篇,下一次更新应该就会开始讲解表面Shader,而

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

版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTreeView/QTableView中利用QStandardItem实现复选框三种形态变化     本文地址:http://techieliang.com/2017/12/729/ 文章目录 1. 介绍 2. 源码  2.1. using_checkbox_item.h  2.2. using_checkbox_item.cpp 3. 说明 1. 介绍 复选框有三种形态:全选对勾.全不选

c++动态内存开辟之 new 的三种形态

1.new 操作符  与   操作符 new //new                new操作符 //delete             delete操作符 //operator new       操作符new //operator delete    操作符delete void main() { Date *p_date = (Date *)operator new (sizeof (Date));  //只是开辟空间 new (p_date)Date (1, 1, 1);     

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

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/42060963 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 邮箱: [email protected] 文章开头,先给自己诚求个游戏研发实习的好去处. 浅墨今年1月.明年上半年有近半年的空暇时间可供实习. 近5年游戏编程经验,能够胜任全职的游戏开发工作.仅仅拿实习生的工资(性价比非常高有

八数码三种用时差距极大的写法

进化史,一种比一种长,一种比一种快.不过第三种似乎还不是最终形态. 第一种,傻逼级迭代加深. 去年十一月写的,那时候刚刚学迭代加深,敲了一个钟头才敲完,codevs上直接过,就没太管,觉得这是个水题.实际上呢,看后文. 1 #include<algorithm> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 int sx,sy,lim,

三种创业思维指明你创业之路

当下虽然经济发展越来越快,普遍的创业者都不缺机会和机遇,但是在经济高速发展的环境下, 创业就能随便成功吗? 不见得是这样,反而创业是越来越难了,产品系列分的是越来越细了,经营手法是越来越多了,那些做生意的商人们的思维是越来越不一样了,各式各样. 在当下的经济环境下,有三种比较典型的经营思维. 1.一门心思研发产品,以为研发出个好产品就能发家致富的心思,可是到头来不是栽倒在产品研发费用就是栽在产品市场费用,大部分的都是石沉大海. 这是叫产品思维. 2.针对于那些需要通过店面启动他们生意的商人,他们

二叉树的存储方式以及递归和非递归的三种遍历方式

树的定义和基本术语 树(Tree)是n(n>=0)个结点的有限集T,T为空时称为空树,否则它满足如下两个条件: (1)有且仅有一个特定的称为根(Root)的结点: (2)其余的结点可分为m(m>=0)个互不相交的子集T1,T2,T3-Tm,其中每个子集又是一棵树,并称其为子树(Subtree). 树形结构应用实例: 1.日常生活:家族谱.行政组织结构:书的目录 2.计算机:资源管理器的文件夹: 编译程序:用树表示源程序的语法结构: 数据库系统:用树组织信息: 分析算法:用树来描述其执行过程:

【系统篇】小议三种函数调用约定

小议三种函数调用约定 __cdecl.__stdcall.__fastcall是C/C++里中经常见到的三种函数调用方式.其中__cdecl是C/C++默认的调用方式,__stdcall是windows API函数的调用方式,只不过我们在头文件里查看这些API的声明的时候是用了WINAPI的宏进行代替了,而这个宏其实就是__stdcall了. 三种调用方式的区别相信大家应该有些了解,这篇文章主要从实例和汇编的角度阐述这些区别的表现形态,使其对它们的区别认识从理论向实际过渡. __cdecl: C

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

SIM卡物理尺寸的发展是逐渐轻薄化,尺寸逐渐缩小的一个过程,最早手机中的卡都是2FF,2003年国际标准提出3FF,当前很多终端都使用这种形态的卡,4FF在2011年的国际标准会议中提出,2012年纳入国际标准 小编最近入手一款新手机,正在嘚瑟的时候,突然发现还得需要剪卡,虽说自己有一个剪卡器,但小编这笨手笨脚,还是不要拿自己当小白鼠的好,果断去找移动MM来解决~ 但MM却说,剪卡可能会剪坏,打算给小编换张新的卡,小编心里纳了个闷,这换手机时究竟什么时候能对原来的SIM卡直接裁剪,什么时候不能呢