Effective C++ 条款49 了解new handler的行为

1. 当operator new无法满足某一内存分配需求时,它会先调用一个客户指定的错误处理函数(如果客户未指定,它就会抛出异常),所谓的new-handler.为了指定这个"用以处理内存不足"的函数,客户必须调用set_new_handler,那是声明于<new>的一个标准库函数:

namespace std{
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}

new_handler是一个typedef,它的类型是指向参数和返回值都为void的函数的函数指针,set_new_handler的new_handler型参数用于指定当无法分配足够内存时调用的函数,set_new_handler返回的函数指针指向在此之前用处处理内存不足当马上就要被替换的函数,set_new_handler的用法如下:

//outOfMem是无法分配足够内存时,应该被调用的函数
void outOfMem(){
    std::cerr<<"Unable to satisfy  request for mempry/n"<<endl;
    std::abort();
}
int main(){
    std::set_new_handler(outOfMem);
    int* pBigDataArray=new int[1000000000];
    ...
}

如果operator new无法分配1000000000个int变量所需空间,它不会立即抛出异常,而是调用outOfMem,因为outOfMem已经被设置为默认的new-handler.

一个设计良好的new-handler应该做以下事情:

1). 让更多内存可被使用.实现这个目的的策略之一是,在程序一开始就分配一大块内存,当new-handler第一次被调用(说明内存不足)时,就把它们归还给程序使用.

2). 安装另一个new-handler,如果当前new-handler无法获取更多内存但它知道另一个new-handler有此能力,它可以调用set_new_handler将那个new-handler设为默认new-handler使得下一次operator new调用的new-handler是最新的那个(令一种策略是令当前new-handler修改自己的行为,方法是让它修改会影响当前new-handler行为的static数据,namespace数据,global数据等)

3). 卸除new-handler.将NULL指针传给set_new_handler,当operator new分配内存不成功时抛出异常.

4). 抛出bad_alloc(或派生自bad_alloc的)异常.这样的异常不会被operator new捕捉,因而会被传递到内存索求处.

5). 不返回.调用abort或exit.(正如outOfMem所做的那样)

2. 有时对于不同类,希望以不同new-handler处理operator new内存分配失败的情况.虽然C++不支持class专属之new-handler,但可以自己实现这种行为,只需令每一个class提供自己的set_new_handler和operator new即可.其中set_new_handler用于设定class专属之new-handler,operator new确保在分配class内存时以class专属之new-handler替换global new-handler,并在class专属之new-handler完成职责后将默认new-handler替换为global new-handler.

假设要处理Widget class的内存分配失败情况,那么以上策略实现如下:

class Widget{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static std::operator new(std::size_t size) throw(std::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;
}

最后,Widget的operator new做以下事情:

1). 调用标准set_new_handler,告知Widget的错误处理函数,即将Widget的new-handler安装位global new-handler.

2). 调用global operator new用来实际分配内存,如果分配失败,global operator new会调用Widget的new-handler,因为那个函数才刚被安装为new-handler.如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常,在此情况下Widget的operator new必须恢复原本的global new-handler,然后再传播该异常,这就需要将采用条款13的思想——以资源管理对象.

3). 最后,如果global operator new成功分配一个Widget对象所用内存,Widget的operator new应该返回一个指针,指向分配所得.

结合资源管理类实现Widget::operator new如下:

class NewHandlerHolder{
public:
    explict NewHandlerHolder(std::new_handler nh):handler(nh){}
    ~NewHandler(){std::set_new_handler(handler);}
private:
    std::new_handler handler;  //用于保存global new-handler
    NewHandlerHolder(const NewHandlerHolder&);
    NewHandlerHolder& operator=(const NewHandlerHolder&);
}
void* Widget::operator new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(currentHandler));//安装new-handler并使h保存global new-handler
    return ::operator new(size);  //h析构时恢复global new-handler
}

Widget的客户类似这样使用其new-handler:

void ourOfMem();
Widget::set_new_handler(outOfMem);
Widget* pw1=new Widget;  //如果内存分配失败,调用的是outOfMem
std::string *ps=new string;   //如果内存分配失败,调用的是global new-handler
Widget::set_new_handler(0);
Widget* pw2=new Widget;  //如果内存分配失败,直接抛出异常

以上代码具有一般化,因此可以考虑将其设为模板,由其他类继承,从而继承这种"可以设定类之专属new-handler"的能力:

template<typename T>
class NewHandlerSupport{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static std::operator new(std::size_t size) throw(std::bad_alloc);
    ...
private:
    static std::new_handler currentHandler;
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler=0;
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 new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(currentHandler));//安装new-handler并使h保存global new-handler
    return ::operator new(size);  //h析构时恢复global new-handler
}

有了这个class template,就可以为Widget和其他类添加set_new_handler支持能力了——只要令Widget继承自NewHandlerSupport<Widget>就好:

class Widget:public NewHandlerSupport<Widget>{
    ...
}

类模板NewHandlerSupport看起来相当奇怪,因为参数T从未被使用,实际上参数T只是用来区分不同的derived class,使继承自NewHandlerSupport的每一个class都有自己独立的static成员变量currentHandler.至于Widget继承自一个以Widget为参数的类模板具现化的类,这是被C++允许的,而且拥有自己的名称——"怪异的循环模板模式"(curiously recurring template pattern;CRTP).

NewHandlerSupport类模板的构建使得"为任何class添加一个它们专属的new-handler"成为易事,然而"mixin"风格的继承亦会导致多重继承的争议.

3. 直至1993年之前,C++还要求operator new必须在无法分配足够内存时返回NULL,虽然新一代的operator应该抛出bad_alloc异常,但原本的"nothrow"形式的operator new仍然保存下来,定义于头文件<new>,调用方法如下:

class Widget{...}
Widget* pw1=new Widget;

由于以上new Widget表达式发生两件事:调用nothrow版的operator new,调用Widget的默认构造函数,因而尽管nothrow版的operator new保证不抛出异常,但这并不能阻止Widget的默认构造函数抛出异常,因而nothrow new不能阻止new表达式抛出异常.

时间: 2024-12-28 17:25:15

Effective C++ 条款49 了解new handler的行为的相关文章

Effective C++ 条款49

了解new-handler的行为 本节条款讲述的技术是,在operator new抛出异常以前,会先调用一个客户指定的错误处理函数:new-handler.当内存分配失败的时候如何自定义并使用这个内存异常处理函数.关键语句就是set_new_handler.作者重点强调如何对于class的内存分配,实现不同的内存分配错误处理函数. 我们先来举个书上的例子,先弄明白如何使用new-handler技术. 如下代码: void outOfMem() { std::cerr<<"Unable

《Effective C++》:条款49:了解new-handler的行为

C++内存是由程序员手动管理的,不像Java或.net有垃圾回收机制.C++内存管理主要是分配例程和归还例程(allocation and deallocation routines),即operator new和operator delete,还有一个配合的角色new-handler.当涉及到数组时,上面提到的operator new和operator delete就会变为operator new[]和operator delete[]. 内存管理在多线程环境下更为复杂,因为heap是一个可被

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数

Effective C++ 条款3 尽可能用const

1. const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体.用const修饰指针,如果const出现在*之前,表明指针不能更改所指向的对象的内容,如果const出现在*之后,表明指针只能指向同一块内存.另外int const*p和const int*p含义相同.如果对象成员有普通指针,那么构造该类的一个const对象时,const修饰使得该指针只能指向同一块内存,但指针指向的内容可以改变. 2. 将某些东西声明为const可以帮助编译器侦测出错误用法. 3. 编译器强制实

STL笔记(5)条款49:学习破解有关STL的编译器诊断信息

STL笔记(5)条款49:学习破解有关STL的编译器诊断信息 条款49:学习破解有关STL的编译器诊断信息 用一个特定的大小定义一个vector是完全合法的, vector<int> v(10);    // 建立一个大小为10的vector 而string在很多方面像vector,所以你可能希望可以这么做: string s(10);        // 常识建立一个大小为10的string 这不能编译.string没有带有一个int实参的构造函数.我的一个STL平台像这样告诉我那一点: e

effective c++ 条款13 use object to manage resources.

请求的系统资源需要最终还回系统,为了避免遗忘返还这个动作,可以利用析构函数在object销毁时自动调用的特点来实现. 简单说就是用object来管理资源. 以内存资源为例 class Investment {}; Investment* creatInvestment(){...} // factory function to produce investment object void main() { Investment* pInv = creatInvestment();//call t

effective c++ 条款3 use const whereever you can

1 const 传达的意思应该是这个变量是常量不能更改 2 const 在 * 左边表示数据是const,在右边表示指针是const // char greeting[] = "hello"; char* p = greeting; //const *: const data //* const: const pointer char const *p1 = greeting; // const data char * const p2 = greeting; // const poi