今天看《编写高质量代码:改善C++程序的150个建议》一书,看到内存管理章节中关于new的论述,简直目瞪口呆,在以前看来再简单不过的new竟然其背后有如此多的玄机!!先摘录一部分内容如下:
C + + 语言一直被认为是复杂编程语言中的杰出代表之一,不仅仅是因为其繁缛的语法规则,还因为其晦涩的术语。下面要讲的就是你的老熟人—n e w :
它是一个内存管理的操作符,能够从堆中划分一块区域,自动调用构造函数,动态地创建某种特定类型的数据,最后返回该区域的指针。该数据使用完后,应调用d e l e t e 运算符,释放动态申请的这块内存。
如果这就是你对n e w 的所有认识,那么我不得不说,你依旧被n e w 的和善外表所蒙蔽着。看似简单的n e w 其实有着三种不同的外衣。
是的,你没有看错,也不用感到惊奇,一个简单的n e w 确实有三种不同的形态,它扮演着三种不同的角色,如下所示:
new operator
operator new
placement new
下面的代码片段展示的是我们印象中熟悉的那个new :
string *pStr = new string("Memory Management");
int *pInt = new int(2011);
这里所使用的new 是它的第一种形态new operator 。它与sizeof 有几分类似,它是语言内建的,不能重载,也不能改变其行为,无论何时何地它所做的有且只有以下三件事,如图3 - 2 所示:
所以当写出“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);
其实n e w o p e r a t o r 背后还藏着一个秘密,即它在执行过程中,与其余的两种形态都发生了密切的关系:第一步的内存申请是通过o p e r a t o r n e w 完成的;而在第二步中,关于调用什么构造函数,则由n e w 的另外一种形态p l a c e m e n t n e w 来决定的。
对于n e w 的第二种形态—内存申请中所调用的o p e r a t o r n e w ,它只是一个长着“明星脸”的普通运算符,具有和加减乘除操作符一样的地位,因此它也是可以重载的。
o p e r a t o r n e w 在默认情况下首先会调用分配内存的代码,尝试从堆上得到一段空间,同时它对事情的结果做了最充分的准备:如果成功则直接返回;否则,就转而去调用一个n e w _h a n d e r ,然后继续重复前面过程,直到异常抛出为止。所以如果o p e r a t o r n e w 要返回,必须满足以下条件之一:
#内存成功分配。
#抛出b a d _ a l l o c 异常。
通常,o p e r a t o r n e w 函数通过以下方式进行声明:
void* operator new(size_t size);注意,这个函数的返回值类型是v o i d * ,因为这个函数返回的是一个未经处理的指针,是一块未初始化的内存,它像极了C 库中的m a l l o c 函数。如果你对这个过程不满意,那么可以通过重载o p e r a t o r n e w 来进行必要的干预。例如:
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); }这里的o p e r a t o r n e w 调用了全局的n e w 来进行内存分配(: : o p e r a t o r n e w ( s i z e ) )。当然这里的全局n e w 也是可以重载的,但是在全局空间中重载v o i d * o p e r a t o r n e w ( s i z e _ t s i z e ) 函数将会改变所有默认的o p e r a t o r n e w 的行为方式,所以必须十二分的注意。还有一点需要注意的是,正像n e w 与d e l e t e 一一对应一样,o p e r a t o r n e w 和o p e r a t o r d e l e t e 也是一一对应的;如果重载了o p e r a t o r n e w ,那么也得重载对应的o p e r a t o r d e l e t e 。
最后,要介绍的是n e w 的第三种形态—p l a c e m e n t n e w 。正如前面所说的那样,p l a c e m e n t n e w 是用来实现定位构造的,可以通过它来选择合适的构造函数。虽然通常情况下,构造函数是由编译器自动调用的,但是不排除你有时确实想直接手动调用,比如对未初始化的内存进行处理,获取想要的对象,此时就得求助于一个叫做p l a c e m e n t n e w 的特殊的o p e r a t o r n e w 了:
p l a c e m e n t n e w 是标准C + + 库的一部分,被声明在了头文件< n e w > 中,所以只有包含了这个文件,我们才能使用它。它在< n e w > 文件中的函数定义很简单,如下所示:
#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 */这就是p l a c e m e n t n e w 需要完成的事。细心的你可能会发现,p l a c e m e n t n e w 的定义与o p e r a t o r n e w 声明之间的区别:p l a c e m e n t n e w 的定义多一个v o i d * 参数。使用它有一个前提,就是已经获得了指向内存的指针,因为只有这样我们才知道该把p l a c e m e n t n e w 初始化完成的对象放在哪里。
#如果仅仅是分配内存,那么应该调用operator new,但初始化不在它的工作职责之内。如果你对默认的内存分配过程不满意,想单独定制,重载operator new 是不二选择。
#如果想在一块已经获得的内存里建立一个对象,那就应该用p l a c e m e n t n e w 。但是通常情况下不建议使用,除非是在某些对时间要求非常高的应用中,因为相对于其他两个步骤,选择合适的构造函数完成对象初始化是一个时间相对较长的过程。