了解new-handle的行为
当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new_handle。(new真正做的事情稍微更复杂些)为了指定该函数,客户必须要调用set_new_handler,那是声明于< new >的一个标准程序库函数:
namespace std{
typedef void(* new_handler)();//new_handler是一个typedef,定义一个指针指向函数,该函数没有参数也不返回任何东西。
new_handler set_new_handler(new_handler p) throw();//set_new_handler则是获得一个new_handler并返回一个new_handler的函数。参数指针指向operator new 无法分配足够内存时该被调用的函数,返回的指针指向set_new_handler被调用前正在执行的那个new_handler函数。
}
void MyOutOfMemory()
{
cout << "Out of memory error!" << endl;
abort();
}
int main()
{
set_new_handler(MyOutOfMemory);
int *verybigmemory = new int[0x1fffffff];
...
}
定义于< new> 中的nothrow常量–该常量用来作为operator new和operator new[]的参数,说明这两个函数分配内存失败时不抛出异常,而是返回一个null指针。
class widget{...};
widget* pw1 = new widget;//如果分配失败,抛出bad_alloc
if(pw1 == 0 )..//该测试一定失败
widget* pw2 = new (std::nothrow) widget;//如果分配失败,返回0
if(pw2 == 0 )...//该测试可能成功
PS:< new >头文件有:
Functions
operator new
operator new[]
operator delete
operator delete[]
set_new_handler
get_new_handler
Types(类型)
nothrow_t
new_handler
bad_alloc
bad_array_new_length
Constants(常量)
- nothrow
编译器自带的new和delete PK 自制版本的new和delete
自制版本的new和delete的好处:
- 用来检测运用上的错误
- 效能胜过缺省版本
- 自制版本的new和delete可轻松收集下列信息:在定制性new和定制性delete之前,理当先收集你的软件如何使用其动态内存,分配区块的大小分布如何?寿命分布如何?倾向于FIFO(先进先出)还是LIFO次序或随机次序来分配和归还?运用型态是否随时间改变,也就是在不同的执行阶段有不同的分配/归还型态吗?任何时刻所使用的最大动态分配量是多少?
齐位(alignment):
许多计算机体系结构要求特定的类型必须放在特定的内存地址上,例如它可能会要求指针的地址必须是4倍数或doubles的地址必须是8倍数。如果没有奉行这个约束条件,可能导致运行期硬件异常。
C++要求所有operator new返回的指针都有适当的对齐(取决于数据类型)。
编写new和delete需要遵守的规则
new
operator new 的返回值,如果有能力供应客户申请的内存,就返回一个指针指向那块内存。如果没有那个能力,就调用new_handle函数,该函数也许能做某些动作将某些内存释放出来,只有当指向该函数的指针是null,operator new才会抛出一个bad_alloc异常。
operator new成员函数会被derived class继承,但是,写定制型内存管理器的一个最常见理由是为针对某特定class对象分配行为提供最优化,却不是为该class的任何derived class。也就是说,针对class X设计的operator new,其行为很典型地只为大小刚好为sizeof(X)而设计。处理该问题的最佳做法就是:改采用标准operator new,例如
void* Base::operator new(std::size_t size) throw(std::bad_alloc) { if(size != sizeof(Base)) return ::operator new(size);//采用标准operator new ... }
delete
- 保证”删除null指针”永远安全
- 如果class专属的operator new将大小有误的分配行为转交::operator new 执行,则必须将大小有误的删除行为转交::operator delete执行。
void* Base::operator delete(void* rawmemory,std::size_t size) throw() { if(rawmemory== 0 ) return;//保证"删除null指针"永远安全 if(size != sizeof(Base)){ ::operator delete(rawmemory);//采用标准operator delete return; } 归还rawmemory所指的内存 return; }
placement new和placement delete
C++在全局作用域内提供以下三种operator new:
throwing | void* operator new (std::size_t size); |
nothrow | void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; |
placement | void* operator new (std::size_t size, void* ptr) noexcept; |
和以下三种operator delete:
ordinary | void operator delete (void* ptr) noexcept; |
nothrow | void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; |
placement | void operator delete (void* ptr, void* voidptr2) noexcept; |
PS:
- C++11中的noexcept大致等同于C++98版的throw();都表示函数不抛出任何异常。
- operator new详细介绍
- operator delete详细介绍
当widget* pw = new widget
时,共有两个函数被调用:一个是用以分配内存的operator new,一个是widget的default构造函数。
假设第一个函数调用成功,第二个函数抛出异常,则运行期系统有责任寻找并调用operator new的相应operator delete版本。
需注意的是,如果找不到与之对应的delete,运行期系统会什么都不做,导致内存泄漏。
因此,若定制new和delete请成对,一个带额外参数的operator new最好有带相同额外参数的对应版operator delete。例如:
class widget{
public:
//placement new
static void* operator new(std::size_t size, std::ostream& logstream) throw(std::bad_alloc);
//ordinary delete
static void* operator delete(void* pmemory) throw();
//placement delete
static void* operator delete(void* pmemory,std::ostream& logstream) throw();
};
对所有与placement new相关的内存泄漏,我们必须同时提供一个正常的operator delete(用于构造期间无任何异常被抛出)和一个带相同额外参数的placement版本(用于构造期间有异常被抛出)。