条款49:了解 new-handler 的行为(Understand the behavior of the new-handler)

条款49:了解 new-handler 的行为

1.1 new-handler

在new操作符抛出异常以响应无法满足的内存请求之前,它会调用客户指定的错误处理函数new-handler。(实际情况比这更复杂)

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

#include <iostream>
// 如果new运算符不能分配足够的内存,要调用的函数
void outOfMem()
{
    std::cerr << "Unable to satisfy request for memory\n";
    std::abort(); //如果不终止,程序会一直尝试调用这个处理函数
}
int main()
{
    std::set_new_handler(outOfMem);
    for(int i=0; i<100000;i++)
    {
        int* pBigDataArray = new int[100000000L];
    }
}

1.2 new-handler任务

设计良好的new-handler函数必须完成下列任务之一:

  1. 让更多内存可被使用。例如:程序一开始就分配一大块内存,而后当 new-handler 第一次被调用,将它们释还给程序使用。
  2. 安装另一个 new-handler。如果当前 new-handler 无法取得更多可用内存,或许它知道另外哪个 new-handler 有此能力。(通过调用set_new_handler,或通过改变全局数据改变自己的行为)
  3. 卸除 new-handler,也就是将 nullptr 传给 set_new_handler,这样 operator new 会在内存分配失败时抛出异常。
  4. 抛出 bad_alloc 或其派生的异常。这样的异常不会被 operator new 捕捉,而会被传播到内存索求处。
  5. 不返回,通常调用 abort 或 exit。

1.3 提出问题

有时你想根据被分配对象的类,用不同的方式处理内存分配失败:

class Widget {
public:
    static std::new_handler set_new_handler(std::new_handler p) noexcept;
    static void* operator new(std::size_t size);
private:
    static std::new_handler currentHandler;
};
std::new_handler Widget::currentHandler = nullptr; // 初始化为空指针
std::new_handler Widget::set_new_handler(std::new_handler p) noexcept
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

class NewHandlerHolder {
public:
    explicit NewHandlerHolder(std::new_handler nh) // 获取当前的new-handler
        :handler(nh) {}  
//类的set_new_handler允许客户端为类指定new-handler
    ~NewHandlerHolder(){std::set_new_handler(handler);}// 释放它

private:
    std::new_handler handler; // 记录下来
    NewHandlerHolder(const NewHandlerHolder&); // 防止拷贝
    NewHandlerHolder& operator=(const NewHandlerHolder&);
};

void* Widget::operator new(std::size_t size) throw(std::bad_alloc){
    // 安装Widget的new-handler
    NewHandlerHolder h(std::set_new_handler(currentHandler));  
    return ::operator new(size); // 分配内存或抛出异常
} // 恢复全局的new-handler

Widget的客户像下面这样使用它的新处理功能:

void outOfMem(); // 函数声明
Widget::set_new_handler(outOfMem); // 将outOfMem设置为Widget的新处理函数
Widget* pw1 = new Widget; // 如果内存分配失败,调用outOfMem
std::string* ps = new std::string; //如果内存分配失败,调用全局新处理函数
Widget::set_new_handler(nullptr); // 设置Widget特定的新处理函数为空
Widget* pw2 = new Widget; // 如果内存分配失败, 立即抛出异常(Widget没有特定的新处理函数)

1.4 解决办法

不同的类,实现这个方案的代码都是相同的,所以应该能够重用它。一种简单的方法是创建一个“mixin风格”的基类,它的设计目的是允许派生类继承一个特定的功能。在本例中,即设置类专用的new-handler。然后将基类转换为模板,以便支持不同类型。

template<typename T> // “mixin分割” 基类,用于支持类特定的set_new_handler
class NewHandlerSupport { 
public:  
    static std::new_handler set_new_handler(std::new_handler p) noexcept;
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ... // 其他版本的operator new
private:
    static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handler p) noexcept
{
    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));
    return ::operator new(size);
}
// 这会将每个currentHandler初始化为空指针
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = nullptr;

class Widget : public NewHandlerSupport<Widget> {
    ... // 和之前一样,但没有set_new_handler或operator new的声明
};  
//这里用到了一种编程技巧, CRTP(curious recurring template pattern,奇异循环模板模式)

1.5 nothrow new

C++的另一种new运算符,提供了传统的“分配失败便返回空指针”。这被称为“nothrow”形式,部分原因是它们在使用new的地方使用了nothrow对象(定义在头文件 中)

class Widget { ... };
Widget* pw1 = new Widget; // 如果分配失败,抛出 bad_alloc 
if (pw1 == 0) ... // 这个测试必然失败
Widget* pw2 = new (std::nothrow) Widget; // 如果Widget分配失败,返回空指针
if (pw2 == 0) ... // 这个测试可能成功

1.6 总结

  1. set_new_handler允许指定一个在无法满足内存分配请求时调用的函数。
  2. nothrow new的用途有限,因为它只适用于内存分配,后续的构造函数调用仍然可能抛出异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值