Item 49:new handler的行为

Item 49: Understand the behavior of the new-handler.

new申请内存失败时会抛出"bad
alloc"
异常,此前会调用一个由std::set_new_handler()指定的错误处理函数(”new-handler”)。

set_new_handler()

“new-handler”函数通过std::set_new_handler()来设置,std::set_new_handler()定义在<new>中:

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

throw()是一个异常声明,表示不抛任何异常。例如void
func() throw(Exception1, Exception2)
表示func可能会抛出Exception1Exception2两种异常。

set_new_handler()的使用也很简单:

void outOfMem(){
    std::cout<<"Unable to alloc memory";
    std::abort();
}
int main(){
    std::set_new_handler(outOfMem);
    int *p = new int[100000000L];
}

new申请不到足够的内存时,它会不断地调用outOfMem。因此一个良好设计的系统中outOfMem函数应该做如下几件事情之一:

  • 使更多内存可用;
  • 安装一个新的”new-handler”;
  • 卸载当前”new-handler”,传递nullset_new_handler即可;
  • 抛出bad_alloc(或它的子类)异常;
  • 不返回,可以abort或者exit

类型相关错误处理

std::set_new_handler设置的是全局的bad_alloc的错误处理函数,C++并未提供类型相关的bad_alloc异常处理机制。
但我们可以重载类的operator
new
,当创建对象时暂时设置全局的错误处理函数,结束后再恢复全局的错误处理函数。

比如Widget类,首先需要声明自己的set_new_handleroperator
new

class Widget{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler current;
};

// 静态成员需要定义在类的外面
std::new_handler Widget::current = 0;
std::new_handler Widget::set_new_handler(std::new_handler p) throw(){
    std::new_handler old = current;
    current = p;
    return old;
}

关于abortexitterminate的区别:abort会设置程序非正常退出,exit会设置程序正常退出,当存在未处理异常时C++会调用terminate
它会回调由std::set_terminate设置的处理函数,默认会调用abort

最后来实现operator
new
,该函数的工作分为三个步骤:

  1. 调用std::set_new_handler,把Widget::current设置为全局的错误处理函数;
  2. 调用全局的operator
    new
    来分配真正的内存;
  3. 如果分配内存失败,Widget::current将会抛出异常;
  4. 不管成功与否,都卸载Widget::current,并安装调用Widget::operator
    new
    之前的全局错误处理函数。

重载operator new

我们通过RAII类来保证原有的全局错误处理函数能够恢复,让异常继续传播。关于RAII可以参见Item
13
。 先来编写一个保持错误处理函数的RAII类:

class NewHandlerHolder{
public:
    explicit NewHandlerHolder(std::new_handler nh): handler(nh){}
    ~NewHandlerHolder(){ std::set_new_handler(handler); }
private:
    std::new_handler handler;
    NewHandlerHolder(const HandlerHolder&);     // 禁用拷贝构造函数
    const NewHandlerHolder& operator=(const NewHandlerHolder&); // 禁用赋值运算符
};

然后Widget::operator
new
的实现其实非常简单:

void * Widget::operator new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(current));
    return ::operator new(size);    // 调用全局的new,抛出异常或者成功
}   // 函数调用结束,原有错误处理函数恢复

使用Widget::operator new

客户使用Widget的方式也符合基本数据类型的惯例:

void outOfMem();
Widget::set_new_handler(outOfMem);

Widget *p1 = new Widget;    // 如果失败,将会调用outOfMem
string *ps = new string;    // 如果失败,将会调用全局的 new-handling function,当然如果没有的话就没有了
Widget::set_new_handler(0); // 把Widget的异常处理函数设为空
Widget *p2 = new Widget;    // 如果失败,立即抛出异常

通用基类

仔细观察上面的代码,很容易发现自定义”new-handler”的逻辑其实和Widget是无关的。我们可以把这些逻辑抽取出来作为一个模板基类:

template<typename T>
class NewHandlerSupport{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler current;
};

template<typename T>
std::new_handler NewHandlerSupport<T>::current = 0;

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw(){
    std::new_handler old = current;
    current = p;
    return old;
}

template<typename T>
void * NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(current));
    return ::operator new(size);
}

有了这个模板基类后,给Widget添加”new-handler”支持只需要public继承即可:

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

其实NewHandlerSupport的实现和模板参数T完全无关,添加模板参数是因为handler是静态成员,这样编译器才能为每个类型生成一个handler实例。

nothrow new

1993年之前C++的operator
new
在失败时会返回null而不是抛出异常。如今的C++仍然支持这种nothrow的operator
new

Widget *p1 = new Widget;    // 失败时抛出 bad_alloc 异常
assert(p1 != 0);            // 这总是成立的

Widget *p2 = new (std::nothrow) Widget;
if(p2 == 0) ...             // 失败时 p2 == 0

“nothrow new” 只适用于内存分配错误。而构造函数也可以抛出的异常,这时它也不能保证是new语句是”nothrow”的。

本文地址:http://harttle.com/2015/09/17/effective-cpp-49.html

时间: 2024-10-12 13:14:24

Item 49:new handler的行为的相关文章

读书笔记 effective c++ Item 49 理解new-handler的行为

1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨论推迟到本条款结束的时候. 1.1 调用set_new_handler来指定全局new-handler 在operator new由于不能满足内存分配要求而抛出异常之前,它会调用一个客户指定的叫做new-handler的错误处理函数.(这也不是完全正确的.Operator new的真正行为更加复杂.

Effective JavaScript Item 49 对于数组遍历,优先使用for循环,而不是for..in循环

本系列作为Effective JavaScript的读书笔记. 对于下面这段代码,能看出最后的平均数是多少吗? var scores = [98, 74, 85, 77, 93, 100, 89]; var total = 0; for (var score in scores) { total += score; } var mean = total / scores.length; mean; // ? 通过计算,最后的结果应该是88. 但是不要忘了在for..in循环中,被遍历的永远是ke

Android 之异步任务(AsyncTask,Handler,Message,looper)

AsyncTask: 3个类型(Params,Progress和Result),4个步骤(onPreExecute(),doInBackground(Params…),onProgressUpdate(Progress…), onPostExecute(Result) ) Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理. 首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异

Item 51:写new和delete时请遵循惯例

Item 51: Adhere to convention when writing new and delete. Item 50介绍了怎样自己定义new和delete但没有解释你必须遵循的惯例. 这些惯例中有些并不直观,所以你须要记住它们! operator new须要无限循环地获取资源.假设没能获取则调用"new handler".不存在"new handler"时应该抛出异常. operator new应该处理size == 0的情况. operator d

Item 50:为什么需要自定义new和delete?

Item 50: Understand when it makes sense to replace new and delete. 我们在Item 49中介绍了如何自定义new的错误处理函数,以及如何为你的类重载operator new. 现在我们回到更基础的问题,为什么我们需要自定义operator new或operator delete? 检测使用错误.new得到的内存如果没有delete会导致内存泄露,而多次delete又会引发未定义行为.如果自定义operator new来保存动态内存

Android--多线程之Handler

转载,感谢http://www.cnblogs.com/plokmju/p/android_handler.html 前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI组件的属性值.但实际开发中,很多地方需要在工作线程中改变UI组件的属性值,比如下载网络图片.动画等等.本篇博客主要介绍Handler是如何发

[转]Android--多线程之Handler

原文:http://www.cnblogs.com/plokmju/p/android_Handler.html 前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI组件的属性值.但实际开发中,很多地方需要在工作线程中改变UI组件的属性值,比如下载网络图片.动画等等.本篇博客主要介绍Handler是如何发送与

Android--多线程之Handler(转)

前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不 允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI组件的属性值.但实际开发中,很多地方需要在 工作线程中改变UI组件的属性值,比如下载网络图片.动画等等.本篇博客主要介绍Handler是如何发送与处理线程上传递来的消息,并讲解 Message的几种传递数据的方式,最后均会以小Demo来演示. Handle

Android--多线程之Handler 前言

前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不 允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI组件的属性值.但实际开发中,很多地方需要在 工作线程中改变UI组件的属性值,比如下载网络图片.动画等等.本篇博客主要介绍Handler是如何发送与处理线程上传递来的消息,并讲解 Message的几种传递数据的方式,最后均会以小Demo来演示. Handle