在C++中,operator new和new operator还是很有区别。new operator是c++内建的,无法改变其行为;而operator new 是可以根据自己的内存分配策略去重载的。
-
operator new
operator new和operator delete有两个重载版本,每个版本支持相关的new表达式和delete表达式:
void *operator new(size_t);
void *operator new[](size_t);
void *operator delete(void*);
void *operatordelete[](void*);
对于operator new你可以对其进行重载,但是必须注意:
(1) 只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,
则如果有new_handler,则调用new_handler;否则如果没要求不抛出异常(以nothrow参数表达),
则执行bad_alloc异常;否则返回0。
(这个调用顺序要记住,可以通过set_new_handler()函数设置new_handler)
(2) 重载时,返回类型必须声明为void*
(3) 重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(4) 重载时,可以带其它参数。
注:当然重载了operator new,你就要重载对应的operator delete。
operator new实际上总是以标准的C malloc()完成,虽然并没有规定非得这么做不可。
同样,operator delete也总是以标准得C free()来实现,不考虑异常处理的话他们类似下面的样子:
extern void* operator new( size_t size )
{
if( size == 0 )
size = 1; // 这里保证像 new T[0] 这样得语句也是可行的
void *last_alloc;
while( !(last_alloc = malloc( size )) )
{
if( _new_handler )
( *_new_handler )(); //调用handler函数
else
return 0;
}
return last_alloc;
}
extern void operator delete( void *ptr )
{
if(ptr) // 从这里可以看出,删除一个空指针是安全的
free( (char*)ptr );
}
-
new operator
当你写string *ps = new string(“Hands up!”)时,你所使用的new是所谓的new operator,它其实干了两件事:一、分配足够的内存(实际大小是大于所创建的对象大小)二、调用对象构造函数,new operator永远干这两件事。上面的那段代码大约反映以下的行为:
void *mem = operator new(sizeof(string));
call string::string(“Hands up!”) on *mem;//只能由编译器完成,用户是不允许这样操作的,也就是说如果你想建立一个堆对象就必须用new操作符,不能直接像上面一样调用构造函数来初始化堆对象。
string ps = static_cast<string>(mem);
也就是说operator new仅仅分配内存(就像malloc一样),我们能够做的仅仅是重载operator new,为自己的类创建一个定制的内存管理方案,这也让我有点明白为什么在重载operator new的时候并没有写调用构造函数的代码,但它确实被调用了,原来都是new operator搞的鬼。编译器看到类类型的new或者delete表达式的时候,首先查看该类是否是有operator new或者operator delete成员,如果类定义了自己的new和delete函数,则使用这些函数为对象分配和释放内存,否则调用标准库版本。如果你想定制自己独有的内存分配过程,你应该重载全局的operator new函数,然后使用new操作符,new操作符会调用你定制的operator new。当然你可以显示的调用:: operator new和:: operator delete强制使用全局的库函数。下面:
Type *p = :: new Type;//该new operator会调用全局的operator new ::delete p;//该delete operator会调用全局的operator delete 建立数组时new操作符(new[])的行为与单个对象建立(new)有少许不同:第一是内存不再调用用operator new函数进行分配,代替以operator new[]函数(常称作array new)。它与operator new一样能被重载,允许定制数组的内存分配,就象定制单个对象内存分配一样。
-
定位new表达式
类似于constructor成员,有第三种new表达式,称为定位new(placement new)。定位new表达式在已分配的与原始内存中初始化一个对象,他与new的其他版本不同,它不分配内存。相反,它接受指向已分配好但未构造的内存指针,并在该内存中初始化一个对象。实际上,定位new表达式使我们在特定的、预分配的内存地址构造一个对象。它可以定义类的任何构造函数。
new(place_address) type new(place_address) type(initializer_list)
place_address必须是一个指针,而initializer_list提供了初始化列表(可能是空的),以便在构造新分配的对象中使用。
- 使用注意点
如果只考虑分配和释放,内存管理基本要求是“不重不漏”:既不重复 delete,也不漏掉 delete。也就说我们常说的 new/delete 要配对,“配对”不仅是个数相等,还隐含了 new 和 delete 的调用本身要匹配,不要“东家借的东西西家还”。例如:
(1)用系统默认的 malloc() 分配的内存要交给系统默认的 free() 去释放;
(2)用系统默认的 new 表达式创建的对象要交给系统默认的 delete 表达式去析构并释放;
(3) 用系统默认的 new[] 表达式创建的对象要交给系统默认的 delete[] 表达式去析构并释放;
(4) 用系统默认的 ::operator new() 分配的的内存要交给系统默认的 ::operator delete() 去释放;
(5) 用 placement new 创建的对象要用 placement delete (为了表述方便,姑且这么说吧)去析构(其实就是直接调用析构函数);
(6)从某个内存池 A 分配的内存要还给这个内存池。
(7)如果定制new/delete,那么要按规矩来。见EffectiveC++相关条款。