new_handler
本节主要讲了new分配内存失败的情况下会发生干什么。
本节的主要一些名词:
std::new-handler:一个函数指针类型
std::set_new_handler:一个函数用于设置当前的异常处理程序,返回前一个异常处理程序的指针
有定义如下:
namespace std {
typedef void (*new_handler) ();
new_handler set_new_handler(new_handler p) throw();
}
//典型用法
void outofmem(){
std::cerr<<"失败"<<endl;
std::abort(); //流产 直接终止程序运行
}
写日志的目的是为了记载自己觉得很好的东西,所以尽量多写自己觉得稀奇的,自己思考的东西。
下面,如果我有一个想法:我想让我自己设计的类拥有自己的异常处理函数,该怎么设计?
示例代码1:
1 int _tmain(int argc, _TCHAR* argv[]){
2 int *ptr = new int[500000000L];
3 return 0;
4 }
以上代码未指定任何异常函数,之后结果为:

实验表明此时的运行结果和指定为NULL一致。
代码示例2:
1 void outofmem(){
2 cerr << "failed" << endl;
3 std::abort();
4 }
5 int _tmain(int argc, _TCHAR* argv[]){
6 set_new_handler(outofmem);
7 int *ptr = new int[500000000L];
8 return 0;
9 }
以上代码,通过set_new_handler函数设置了自己的异常处理函数,其中std::abort();意为直接结束程序。
运行结果如下:

从以上运行结果可知:
- 上述代码确实执行了outofmem函数。
- 代码的结束原因是调用了abort函数。
再来,如果我要弄一个拥有自己的的异常处理程序的类呢?
1 class mydata{
2 private:
3 int a = 0;
4 public:
5 static void outofmem(){
6 cerr << "failed" << endl;
7 std::abort();
8 }
9 static std::new_handler set_new_handler(){
10 std::new_handler p = std::set_new_handler(outofmem);
11 return p;
12 }
13 };
14 int _tmain(int argc, _TCHAR* argv[]){
15 mydata::set_new_handler();
16 mydata *p = new mydata[500000000L];
17 return 0;
18 }
以上代码通过将异常处理函数和设置异常处理程序函数定义在类的内部,实现类专属的异常处理程序。
但是上面的代码存在问题:
上述代码将原有的额异常处理程序替换掉之后却没有还原,导致后面的程序如果再出错的话会沿袭上一个类设置的异常处理程序,这显然不太科学。
Effective上面提供了一种科学的办法:
do it for me :创建一个小型的基类,这个基类是专属我的存在(不与别的类共享),存在的目的是专门为我做某件事情。
1 //添加辅助类 使得分配失败时恢复原来的异常处理函数 2 class newhandlerholder{ 3 public: 4 //explicit的构造函数 需要精确匹配 5 explicit newhandlerholder(std::new_handler nh) :handler(nh){ 6 7 } 8 ~newhandlerholder(){ 9 //将源指针还原 这部很重要 10 std::set_new_handler(handler); 11 } 12 //阻止拷贝copy 13 newhandlerholder(const newhandlerholder&) = delete; 14 newhandlerholder& operator=(const newhandlerholder&) = delete; 15 private: 16 std::new_handler handler; 17 }; 18 19 //定义类 20 class Widget{ 21 public: 22 static std::new_handler set_new_handler(std::new_handler p) throw(); 23 static void* operator new(std::size_t size) throw(std::bad_alloc); 24 private: 25 static std::new_handler currenthandler; 26 int x = 10; 27 }; 28 //初始化static成员变量 29 std::new_handler Widget::currenthandler = NULL; 30 //为currenthandler设置新值并返回之前的值 31 std::new_handler Widget::set_new_handler(std::new_handler p) throw(){ 32 std::new_handler old_handler = currenthandler; 33 currenthandler = p; 34 return old_handler; 35 } 36 //重载new操作符 37 void* Widget::operator new(std::size_t size) throw(std::bad_alloc){ 38 newhandlerholder h(std::set_new_handler(currenthandler)); 39 return ::operator new(size); 40 } 41 //异常处理程序 42 void outofmem(){ 43 cerr << "failed" << endl; 44 std::abort(); 45 } 46 47 int _tmain(int argc, _TCHAR* argv[]){ 48 //调用 49 Widget::set_new_handler(outofmem); 50 Widget *p = new Widget; 51 return 0; 52 }
以上代码有几点值的说明:
-
Widget重载了操作符operator new而b不是operator new[]。
- operator new 的返回值是void *因为这个操作符最终是要被表达式new调用的,后者进行真正的构造函数返回符合类型的指针。
-
newhandlerholder h(std::set_new_handler(currenthandler));这句话的精妙之处在于,它得将指针放在类中,不管这里分配失败还是成功h都将因为离开作用域而还原原有指针。 - 这里对于类Widget只要调用一次Widget::set_new_handler(outofmem);今后的每次new都是这个异常处理程序。
-
newhandlerholder(const newhandlerholder&) = delete;这是删除复制copy函数,比起老版本的设置为private这个就直观很多,就会阻止拷贝的意思。
关于do it for me :
观察上面的代码,内存管理类和具体的类型时没有关系的,如果有多个类是否每个类里面都要设置一个currenthandler和相关的配套函数,答案是否定的,这种情况下可以将独立的功能抽取出来,做成一个类,然后让其他类去继承,这个类除了一些static的函数和数据外没有其它东西,目的是为了让每个继承的类拥有自己的一套独立的变量和函数扶不干扰,这样这个被继承的类就只为继承他的那个类提供专用的功能,简称:do it for me

浙公网安备 33010602011771号