条款49:了解new-handle行为

多线程下的内存管理与单线程下是完全不同的,因为heap是一个可以被全局改动的资源,所以所有的线程都有可能去访问这一资源,这回导致很多的race_conditions。

 
当operator new未取得想要的内存的时候,会调用一个用户指定的处理函数,new_handler。 这个函数可以使用set_new_handler来进行指定。
1 namespace std{
2     typedef void(*new_handler)();//没有参数以及返回值
3     new_handler set_new_handler(new_handler p) throw();
4 }
当operator new无法满足内存申请条件的时候,其就会不停的调用new-handler函数,所以说一个设计良好的new-handler应该需要满足下面的要求:
    让更多的内存被使用
    当当前的handler无法满足条件的时候,安装另一个new-handler.
    卸除new_handler,将null指针传给set_new_handler,,此时operator new 在无法分配内存的时候就会抛出异常
    抛出bad_alloc异常:这样的异常会被传播到内存的索求处
    不返回:调用abort或者exit
 
 
当需要以不同的方式去处理不同的class内存分配失败的情况的时候,应该为相应的特殊的class提供自己的set_new_handler以及operator_new。这样operator new会在分配内存的时候确保使用class专属的new_handler替代global的new handler.
 
一个Widget分配资源的简单例子:
 1 class NewHandlerHolder{
 2 public:
 3     explicit NewHandlerHolder(std::new_handler nh)
 4     :handler(nh){}
 5     ~NewHandlerHolder()
 6     std::set_new_handler(handler);    //将状态恢复到之前的状态
 7 private:
 8     new_handler handler;
 9     NewHandlerHolder(const NewHandlerHolder &);    //禁止这两种操作
10     NewHandlerHolder & operator(const NewHandlerHolder &);
11 };

上面这个就可以被自定义的一个类所使用了:

1 void * Widget::operator new(std::size_t size) throw (std::bad_alloc)
2 {
3     NewHandlerHolder h(std::set_new_handler(currentHandler));//使用上面的RAII将handler资源正确的管理。
4     return ::operator new(size);//使用自定义的new
5 }

实际上,可以将上面的NewHandlerHolder设计成为一个基类模板,这样derived_class科技继承他们需要的set_new_handler以及operator_new,template用于保证每一个derived_class获得一个实体互异的currentHandler成员变量。下为这个模板:

 1 template<typename T>   //这里的typename T 并未被使用,作用下面会说
 2 class NewHandlerSupport{
 3 public:
 4     static std::new_handler set_new_handler(std::new_handler p) throw();
 5     static void * operator new(std::size_t size) throw(std::bad_alloc);
 6     ...
 7 private:
 8     static std::new_handler currentHandler;
 9 };
10 template<typename T>
11 static std::new_handler 
12 NewHandlerSupport<T> set_new_handler(std::new_handler p) throw()
13 {
14     std::new_handler oldHandler = currentHandler;
15     currentHandler = p;
16     return oldHandler;
17 }
18 template<typename T>
19 static void * 
20 NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
21 {
22     NewHandlerHolder h(std::set_new_handler(currentHandler));
23     return ::operator(size);//调用全局的operatornew,防止递归的调用自己。
24 }

有了这个模板,那么为Widget提供专属的set_new_handler以及operator都是很容易的了。继承上面的模板并特例化就可以了:

1 class Widget : public NewHandlerSupport<Widget>{
2     ...
3 };
这样就有了专属的set_new_handler以及operator new。
上买了的typename T 没有被使用,实际上也不需要, 这个T的目的只是为了使得不同的继承自NewHandlerSupport的子类有着其不同的实例函数。(主要是为了static变量,currentHandler)。T只是用来区分不同的derived_class. (因为上面T实例化成了Widget)
 
还有就是关于nothrow的问题,使用new (std::nothrow) Widget不会在new上产生异常,只会返回一个null指针,但是如果new正常还有可能在构造过程中产生异常,因此依然没有异常保证。没事不要用new (std::nothrow) sth;
 
小结:
    set_new_handler允许用户指定一个函数在内存无法分配时调用
    Nothrow new只能保证new不抛异常,构造仍然得不到保证。
 
posted @ 2015-10-18 17:14  eversliver  阅读(571)  评论(0编辑  收藏  举报