标签:restore == 笔记 回调 阶段 return event .com install
当操作符new不能满足内存分配请求的时候,它就会抛出异常。很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做。你仍然会看到这种旧行为,但是我会把关于它的讨论推迟到本条款结束的时候。
在operator new由于不能满足内存分配要求而抛出异常之前,它会调用一个客户指定的叫做new-handler的错误处理函数。(这也不是完全正确的。Operator new的真正行为更加复杂。详细内容在Item 51中描述。)为了指定内存溢出处理(out-of-memory-handling)函数,客户可以调用set_new_handler函数,这个标准库函数被声明在<new>中:
1 namespace std { 2 typedef void (*new_handler)(); 3 new_handler set_new_handler(new_handler p) throw(); 4 }
正如你所看到的,new_handler是一个函数指针的typedef,这个函数没有参数没有返回值,set_new_handler是一个参数和返回值都为new_handler的函数。(函数set_new_handler声明结束处的”throw()”是一个异常指定(exception specification)。从本质上来说它的意思是说这个函数不会抛出任何异常,然而事实更加有意思。详细内容见Item 29。)
set_new_handler的参数是指向函数的指针,operator new会在请求的内存无法分配的情况下调用这个函数。Set_new_handler的返回值也是指向函数的指针,返回的是在调用set_new_handler之前调用的new_handler函数(也就是在new_handler被替换之前的函数)。
你可以像下面这样使用set_new_handler:
1 // function to call if operator new can’t allocate enough memory 2 void outOfMem() 3 { 4 std::cerr << "Unable to satisfy request for memory\n"; 5 std::abort(); 6 } 7 8 int main() 9 { 10 std::set_new_handler(outOfMem); 11 int *pBigDataArray = new int[100000000L]; 12 ... 13 }
如果operaotr new无法为100,000,000个整数分配内存,就会调用outOfMem,也就是输出一个error信息之后程序终止(abort)。(顺便说一下,考虑在向cerr中写入error信息期间如果必须动态的分配内存会发生什么。。)
当operator new不能满足一个内存请求的时候,它会反复调用new-handler函数直到它发现有足够的内存可以分配了。引起这些函数被反复调用的代码在Item 51中可以找到,但是这种高级别的描述信息足够让我们得出结论:一个设计良好的new-handler函数必须能够做到如下几点。
这些选择让你在实现new-handler的时候有相当大的灵活性。
有时候你想用不同方式来处理内存分配失败,这依赖于需要分配内存的对象所属的类:
1 class X { 2 public: 3 static void outOfMemory(); 4 ... 5 }; 6 class Y { 7 public: 8 static void outOfMemory(); 9 ... 10 }; 11 X* p1 = new X; // if allocation is unsuccessful, 12 // call X::outOfMemory 13 Y* p2 = new Y; // if allocation is unsuccessful, 14 // call Y::outOfMemory
C++没有为类提供指定的new-handlers,但也不需要。你可以自己实现这种行为。你可以使每个类提供自己版本的set_new_handler和operator new。类中的set_new_handler允许客户为类提供new_handler(就像标准的set_new_handler允许客户指定全局的new-handler一样)。类的operator new确保为类对象分配内存时,会使用其指定的new-handler来替代全局new-handler。
假设你想对Widget类对象的内存分配失败做一下处理。当operator new不能为Widget对象分配足够的内存的时候你必须跟踪一下函数调用过程,所以你要声明一个类型为new_handler的static成员,来指向这个类的new-handler函数。Widget将会是下面这个样子:
1 class Widget { 2 public: 3 static std::new_handler set_new_handler(std::new_handler p) throw(); 4 static void* operator new(std::size_t size) throw(std::bad_alloc); 5 private: 6 static std::new_handler currentHandler; 7 };
静态类成员必须在类外部定义(除非他们是const整型,见Item 2),所以:
1 std::new_handler Widget::currentHandler = 0; // init to null in the class 2 // impl. File
Widget中的set_new_handler函数会把传递进去的指针(所指向的new-handler函数)保存起来,并且会返回调用set_new_handler之前所保存的指针。这也是标准版本set_new_handler的做法:
1 std::new_handler Widget::set_new_handler(std::new_handler p) throw() 2 { 3 std::new_handler oldHandler = currentHandler; 4 currentHandler = p; 5 return oldHandler; 6 }
最后,Widget的operator new将会做下面的事情:
这里我们以资源处理(resource-handling)类开始,只包含基本的RAII处理操作,包括在构造时获取资源和在在析构时释放资源(Item 13):
1 class NewHandlerHolder { 2 public: 3 explicit NewHandlerHolder(std::new_handler nh) // acquire current 4 : handler(nh) {} // new-handler 5 6 ~NewHandlerHolder() // release it 7 8 { std::set_new_handler(handler); } 9 10 private: 11 12 13 14 std::new_handler handler; // remember it 15 16 NewHandlerHolder(const NewHandlerHolder&); // prevent copying 17 18 19 20 NewHandlerHolder& // (see Item 14) 21 22 operator=(const NewHandlerHolder&); 23 24 };
这会使得Widget的operator new的实现非常简单:
1 void* Widget::operator new(std::size_t size) throw(std::bad_alloc) 2 { 3 NewHandlerHolder // install Widget’s 4 h(std::set_new_handler(currentHandler)); // new-handler 5 6 return ::operator new(size); // allocate memory 7 // or throw 8 9 } // restore global 10 // new-handler 11 12 13 14 void outOfMem(); // decl. of func. to call if mem. alloc. 15 // for Widget objects fails 16 17 Widget::set_new_handler(outOfMem); // set outOfMem as Widget’s 18 // new-handling function 19 20 Widget *pw1 = new Widget; // if memory allocation 21 // fails, call outOfMem 22 23 std::string *ps = new std::string; // if memory allocation fails, 24 // call the global new-handling 25 // function (if there is one) 26 27 Widget::set_new_handler(0); // set the Widget-specific 28 // new-handling function to 29 // nothing (i.e., null) 30 31 Widget *pw2 = new Widget; // if mem. alloc. fails, throw an 32 // exception immediately. (There is 33 // no new- handling function for 34 // class Widget.)
不管在什么类中,实现的这个主题的代码都是一样的,所以我们可以为其设一个合理的目标,就是代码能够在其他地方重用。达到这个目标的一个简单方法是创建一个“混合风格(mixin-style)”的基类,也就是设计一个基类,允许派生类继承单一特定的能力——在这个例子中,这种能力就是为类指定new-handler。然后将基类变为一个模板,于是你可以为每个继承类获得一份不同的类数据的拷贝。
这个设计的基类部分使得派生类能够继承它们都需要的set_new_handler和operator new函数,同时设计的模板部分确保每个继承类获得一个不同的currentHandler数据成员。说起来有些复杂,但是代码看上去很熟悉。事实上,唯一真正不一样的是现在任何类都能够获得这个功能:
1 template<typename T> // “mixin-style” base class for 2 class NewHandlerSupport { // class-specific set_new_handler 3 public: // support 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 ... // other versions of op. new — 7 // see Item 52 8 private: 9 static std::new_handler currentHandler; 10 }; 11 template<typename T> 12 std::new_handler 13 NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw() 14 { 15 std::new_handler oldHandler = currentHandler; 16 currentHandler = p; 17 return oldHandler; 18 } 19 template<typename T> 20 void* NewHandlerSupport<T>::operator new(std::size_t size) 21 throw(std::bad_alloc) 22 { 23 NewHandlerHolder h(std::set_new_handler(currentHandler)); 24 return ::operator new(size); 25 } 26 // this initializes each currentHandler to null 27 template<typename T> 28 std::new_handler NewHandlerSupport<T>::currentHandler = 0;
有了这个类模板之后,向Widget中添加set_new_handler支持就变得容易了:Widget只需要继承自NewHandlerSupport<Widget>。(这可能看上去比较独特,接下来我会进行详细的解释。)
1 class Widget: public NewHandlerSupport<Widget> { 2 ... // as before, but without declarations for 3 4 }; // set_new_handler or operator new
这是Widget提供一个特定的set_new_handler需要做的所有事情。
但是对于Widget继承自NewHandlerSupport<Widget>,你可能还是有些不安。如果是这样,当你注意到NewHandlerSupport模板永远不会使用类型参数T之后你的不安可能会加剧。你没有必要这样。对于每个继承自NewHandlerSupport的类来说,我们所有需要的是一份不同的NewHandlerSupport的拷贝——特别是静态数据成员currentHandler的不同拷贝。模板机制自身会为每个T自动生成currentHandler的一份拷贝,NewHandlerSupport使用这个T来进行实例化。
对于Widget继承自一个使用Widget作为类型参数的模板基类来说,如果这个概念让你感觉眩晕,不要感觉不好。每个人看到开始看到它的时候都会有这种感觉。但是,它是非常有用的技术,它有一个名字,这个名字如果这个概念一样,第一次看到它的人没有人会感觉它很自然,它叫做怪异的循环模板模式(curiously recurring template pattern CRTP)。
我曾经写过一遍文章建议为它起一个更好的名字:do it for me,因为当Widget继承自NewHandlerSupport<Widget>,它真的像是在说:“我是Widget,我需要为Widget继承NewHandlerSupport类“。没有人使用我建议的名字,但是使用“do it for me”来想象一下CRTP可能会帮助你理解模板化的继承会做什么。
有了像NewHandlerSupport这样的模板,为任何需要new-hadler的类添加一个特定的new-handler就会变得容易。混合风格的继承总是会将你引入多继承的主题,在开始进入这个主题之前,你可能想读一下Item 40。
直到1993年,当不能满足分配内存的要求时,C++要求operator new要返回null。现在指定operator new要抛出bad_alloc异常,但是大量的C++是在编译器支持修订版本之前写出来的。C++标准委员会也不想废弃test-for-null的代码,所以它们为operator new提供了一种替代形式,它能够提供传统的“失败产生null(failure-yields-null)”行为。这些形式被叫做“nothrow”形式,某种程度上是因为他们使用了不会抛出异常的对象(定义在头文件<new>中),new在这种情况下被使用:
1 class Widget { ... }; 2 Widget *pw1 = new Widget; // throws bad_alloc if 3 // allocation fails 4 5 if (pw1 == 0) ... // this test must fail 6 7 Widget *pw2 = new (std::nothrow) Widget; // returns 0 if allocation for 8 // the Widget fails 9 10 if (pw2 == 0) ... // this test may succeed
nothrow版本的new不会像从表面上看起来这样可靠,对于异常它没有提供让人信服的保证。对于表达式“new (std::nothrow) Widget”,会发生两件事情。首先,通过调用nothrow版本的operator new来为一个Widget 对象分配足够的内存。如果分配失败了,operator new会返回null指针。然而如果分配成功了,Widget构造函数会被调用,到这个时候,就会世事难料了。Widget构造函数能够做任何它想做的。它自己可能new一些内存,如果是这样,并没有强迫它使用nothrow版本的new。虽然在”new (std::nothrow) Widget”中的operator new不会抛出异常,但是Widget构造函数却可能抛出来。如果是这样,异常会像平时一样传播出去。结论是什么?使用nothrow new只能保证operator new不会抛出异常,不能保证像“new(std::nothrow) Widget”这样的表达式不抛出异常。十有八九,你将永远不会有使用nothrow new的需要。
不论你是使用”普通的”(也就是抛出异常的)new还是nothrow版本的new,重要的是你需要明白new-handler的行为,因为在两种new中都会使用到它。
读书笔记 effective c++ Item 49 理解new-handler的行为
标签:restore == 笔记 回调 阶段 return event .com install
原文地址:http://www.cnblogs.com/harlanc/p/6721163.html