条款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不抛异常,构造仍然得不到保证。