49.了解new-handler的行为(Understand the behavior of the new-handler)
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
当operator new无法满足某一内存分配需求时,他会先调用一个客户指定的错误处理函数,一个所谓的new-handler。该函数无参数,返回void。
void outOfMem(){
std::cerr<<"Unable to satisfy request for memory\n";
std::abort();//另程序中止,不然会一直调用outOfMme()
}
int main(int argc, const char * argv[]) {
std::set_new_handler(outOfMem);
int* p = new int[9999999999999999999L];
return 0;
}
//输出Unable to satisfy request for memory
一个好的new-handler应做以下事情:
1.让更多内存可以被使用。
2.安装另外一个new-handler。当期new-handler无法取得更多内存时,若知道哪个new-handler可以获得更多内存,那么就设置其为new-handler。
3.卸除new-handler。将null传给set_new_handler以卸除。这样new会在内存分配不成功时抛出异常。
4.抛出bad_alloc的异常。这样的异常不会被operator new捕捉,因此会传播到内存索求处。
5.不返回,通常调用abort或exit。
有时候我们希望把new_handler与class绑定,在不同class的operator new无法满足内存分配需求时,调用不同的new_handler.我们将此功能封装于NewHandlerSupport;
//使用RAII操作
//NewHandlerSupport中并不使用typename T
//但此处使用template的目的是位不同的类独立生成一个currentHandler成员变量
template<typename T>
class NewHandlerSupport{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
NewHandlerSupport() = default;
explicit NewHandlerSupport(std::new_handler nh)
{ handler = nh; }//记录旧的new_handler;
~NewHandlerSupport(){
//离开作用域时将new_handler设置回去
std::set_new_handler(handler);
}
private:
static std::new_handler currentHandler;//用来记录将要设置的new_handler
static std::new_handler handler;//用来记录旧的new_handler
NewHandlerSupport(const NewHandlerSupport&);//阻止copying
NewHandlerSupport& operator=(const NewHandlerSupport*);
};
template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p){
std::new_handler oldHandler = currentHandler;//保存旧new_handler
currentHandler = p;//设置新new_handler
return oldHandler;//返回旧new_handler
}
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
//设置currentHandler为new_handler
//并将返回值——旧的new_handler通过构造函数保存至handler
NewHandlerSupport h(std::set_new_handler(currentHandler));
return ::operator new(size);
//将调用h的析构函数,用以将new_handler设置回去
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
template<typename T>
std::new_handler NewHandlerSupport<T>::handler = 0;
class Widget: public NewHandlerSupport<Widget>{
public:
Widget()
{
}
~Widget();
int a[99999999999999999L];//为了分配失败
};
void outOfMem(){
std::cerr<<"Unable to satisfy request for memory\n";
std::abort();
}
int main(int argc, const char * argv[]) {
Widget::set_new_handler(outOfMem);
Widget* pw = new Widget();//如果内存分配失败则调用outOfMem
//之后若分配内存失败将调用其他new_handler
return 0;
}
50.了解new和delete的合理替换时机(Understand when it makes sense to replace new and delete.)
有许多理由需要写个自定的new和delete,包括改善性能,对heap运用错误进行调试,收集heap使用信息;
重载new和delete的理由:
1.用来检测运用上的错误。
例如我们可以自定义new,用来超额分配内存,早客户所得的内存区块前后放置特定的byte签名,若由于一些编程错误导致overruns(写入点在分配器块尾端之后)或underruns(在起点之前),我们可以在自定义的delete种检测并志记。
2.为了收集动态分配内存之使用统计信息。
3.为了增加分配和归还的速度。
4.为了降低缺省内存管理器带来的空间额外开销。
5.为了弥补缺省分配器中的非最佳齐位。
6.为了将相关对象成蔟集中。降低缺页率。
7.为了获得非传统行为。
51.编写new和delete时需固守常规(Adhere to convention when writing new and delete.)
operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.它也应该有能力处理0bytes申请。class专属版本则还应该处理”比正确大小更大(错误)的申请”。
operator delete应该在收到null指针时不做任何事。class专属版本则还应该处理”比正确大小更大(错误)的申请”。
operator new中处理0byte申请的方法是视为1byte申请。c++要求客户要求0byte,operator new也要返回一个合法指针
if(size == 0){
size = 1;
}
operator new还需正确处理比正确大小更大的申请,这种情况发生的场合是derived class继承了带有operator new的base class,但derived class并没有声明operator new,所以new derived();
调用的是base class的operator new。解决方法是遇到这种情况则调用标准的operator new;
if(size != sizeof(Base)){
return ::operator new(size);
}
此种做法包含了处理0byte申请,因为sizeof(Base)不会返回0.原因
我们还需要实现new_handler,在operator new中提供一个无限循环,除非分配成功return,要么调用new_handler;
while(true){
尝试分配size bytes;
if(分配成功)
return;
if(new_handler != NULL)
调用new_handler;
else
throw std::bad_alloc();
}
实现operator new[]只需要分配一块未加工过的内存,因为无法对array之内迄今尚未存在的对象做任何事。甚至不知道每个对象的大小,因为这其中涉及了继承。
operator delete需要保证删除null指针安全。
if(rawMemory == 0)
return;
operator delete需要正确处理比正确大小更大(错误)的申请,原理同new;
if(size != sizeof(Base)){
::operator delete(rawMemory);
return;
}
52.写了placement new也要写placement delete(Write placement delete if you write placement new.)
当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这么做,你的程序可能会发生隐微而时断时续的内存泄露。
当你声明placement new和placement delete,请确定不要无意识(非故意)地掩盖了他们的正常版本。
当写出一个表达式如下:
Widget* pw = new Widget();
其中有两个函数被调用,一是分配内存的operator new,二是 widget的default构造函数。当第一个函数成功,但第二个函数抛出异常。那么C++将寻找与new对应的delete来释放内存。
但假如写了一个class专属的operator new,其接受除了那个一定会有是size_t参数还有其他参数,这样的operator new被成为placement new。
在placement情况下,若new成功但构造失败,那么编译器将会寻找与placement new有相同参数的placement delete。若无声明对应的placement delete,那么将会什么也不做,导致内存泄露。