一、了解new-handler的行为
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();//trhow()是一份异常明细,表示该函数不抛出任何异常
}
set_new_handlerd的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行的那个new-handler函数。 当set_new_handler设置的函数中有分配内存的行为,若operator new 无法满足内存申请时,则会一直调用set_new_handler设置的函数,直到申请内存成功。
- 设计良好的new-handler应该做到哪些?
- 让更多的内存可被使用
让更多的内存可被使用,会使operator new 下一次内存申请成功。实现该策略的一个方法是:程序一开始就分配一大块内存,当new-handle第一次被调用时,将它们还给程序使用。 - 安装另一个new-handelr
目前的new-handelr无法获取更多的可用内存,安装另一个有此能力的new-handelr。或者让当前的new-handelr修改自己的行为,即修改static数据,namespace数据,或global数据。 - 卸除new-handler
将null传给set_new_handler。没有安装handler,则operater new 会在内存分配不成功时抛出异常。 - 抛出bad_alloc(或派生自bad_alloc)的异常 这样的异常不会被operator new 捕捉,因此会被传播到内存索求处。
- 不返回 通常调用abort或exit。
- 如何以不同的方式处理内存分配失败的情况?
定义专属的operator new,如下:
class NewHandlerHolder{
public:
explicit NewHandlerHolder(std::new_handle nh)
: handler(nh){}
~NewHandlerHolder()
{
std::set_new_handler(handler);
}
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
class Widget{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(bad_alloc);
private:
static std::new_handler currentHandler;
};
std::new_handler Widget::currentHandler = 0;
std::new_handler Widget::set_new_handler(std::new_handler p) throw(){
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
void* operator Widget::new(std::size_t size) throw(bad_alloc){
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size); //指明调用全局的new,否则会调用Widget::new
}
- new操作符的查找规则:当new一个类型时,会现在该类型的作用域内查找new操作符,没有则在该类型的上一级作用域,直至全局作用域内的new——即标准的new。
- class专属之set_new_handler
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);
...
private:
static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw(){
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<typename T>
void* NewHandlerSupport<T>::operator Widget::new(std::size_t size) throw(std::bad_alloc){
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size); //指明调用全局的new,否则会调用Widget::new
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
class Widget:public NewHandlerSupport<Widget>{
...
}
注意到NewHandlerSupport模板从未使用到模板参数T。将NewHandlerSupport定义为模板,实际是希望继承自NewHandlerSupport的每一个class类型都有一个独立的static成员变量currentHandler,因为最初就是希望每种类型都有自己的专属new_handler和operator new。T只是用来区分不同的derived classes。
- nothrow.
- nothrow 是C++委员会为兼容之前new返回null的版本而设计的。
- nothrow的缺陷:
new (std::nothrow) Widget 意味着new不抛异常,但是new申请内存成功后,执行构造函数可能会抛出异常,因为构造函数中可能使用的是抛出异常的new,所以nothrow的用处不大。
Widget* pw2 = new (std::nothrow) Widget; //不抛异常,如内存申请失败则返回nul
二、了解new和delete的合理替换时机
- 用来检测运行上的错误
- new所得内存delete掉却不幸失败会导致内存泄露
- 在new所得内存身上多次delete则会导致不确定行为
- 各式各样的错误会导致数据“overruns”(写入点在分配区块的尾端之后)或“underruns”(写入点在分配区块的起点之前)
自定义operator new,便可以超额分配内存,以额外空间(位于客户所的区块之前或后)放置特定的bytw patterns(即签名,signatures)。operator deletes便可以检查上述签名是否原封不动,若否就表示在分配区的某个生命时间点发生了overrun或underrun,这时候operator delete记录那个事实以及惹事生非的指针。 - 为了强化效能
编译器所带的new和delete主要用于一般目的:既可以被长时间执行的程序接受,也可以被少于1秒的程序接受,还要满足处理大块内存、小块内存、大小混合型内存等等,还要考虑破碎内存。因此效率上比较中庸,定制的new和delete性能胜过缺省版本,节省内存,最高可省50%。 - 为了收集使用上的统计数据
定制new和delete可以收集到的信息:
- 分配区块大小
- 倾向于以FIFO或LIFO(后进先出)次序或随机次序来分配和归还
- 在不同的执行阶段有不同的分配/归还形态么?
- 任何时刻所使用的最大动态分配量的最高水位是多少
三、编写new和delete时需固守常规
- C++规定,即使客户要求0 bytes,operator new 也得返回一个合法的指针。
- new 中包含着一个无限循环。如果new-handler不是设计良好的(如第一节所属),则该循环永远不会退出。
- 当operator new被继承时,Derived classed可能会采用base classes中的operator new,应为operator new只是针对Base classes的,应用到Derived classes会产生申请错误——Derived classes对象的内存更大,所以Base classes 中处理这种情况的最佳做法是:将派生类的创建交由::opertor new 处理。
- opertor delete应该在收到null指针时不做任何事。class 专属版本的delete同样需要处理上述Drived classes和Base classed 间的关系。
- 一般不需要重写new 和delete,需要考虑内存对齐等比较复杂的问题。有商业版本即开源的定制new和delete,如,boost库中提供的new和delete更适合分配大量小型对象,
四、写了placement new 也要写placement delete
- placement new (带额外参数的new) 标准版本的operator new 中接受的参数除了有std::size_t之外还有其他参数。placement delete的定义同理。
void* operator new(std::size_t size) throw(std::bad_alloc); //标准版本
void* operator new(std::size_t size, void* pMemory) throw(); //众多palcement版本中最有用的一个
- new时会进行两件事:1)申请内存;2)调用构造。若1)成功,2)失败,则需要运行期系统来释放内存,因为此时申请的内存还没有交到客户手上。
- 提供placement new,也许提供placement delete,否则运行期系统无法找到合适版本的delete,将导致内存泄漏。
- 提供了placement delete,若没有发生异常,delete 时调用的是标准版本的delete。placement delete 只有在“伴随placement new调用而出发的构造函数”出现异常时才会被调用。
- 避免class 专属的news 掩盖其外围作用域中的其他news(包括标准版本的new)。
- C++ 在global作用域中提供以下形式的operator new:
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t, void* ) throw();
void* operator new(std::size_t,const std::nothrow&) throw();
//按如下方式可避免遮掩
class StandardNewDeleteForms{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc){
return ::operator new(size); }
static void* operator delete(void* pMemory) throw(){
return ::delete(pMemory); }
static void* operator new(std::size_t size, void* ptr) throw(){
return ::operator new(size, ptr); }
static void* operator delete(void* pMemory,void* ptr) throw(){
return ::delete(pMemory, ptr); }
static void* operator new(std::size_t size, const std::nothrow& nt ) throw(){
return ::operator new(size, ptr);}
static void* operator delete(void* pMemory,const std::nothrow& nt) throw(){
return ::delete(pMemory, nt);}
};
class Widget : public StandardNewDeleteForms{
public:
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
};
|