在 C++ 中,当 operator new
无法满足内存分配请求时,它会有特定的处理机制,了解 new-handler
的行为对于处理内存分配失败的情况至关重要。下面结合代码详细介绍相关内容。
1. new-handler
基础概念
当 operator new
无法满足内存分配请求时,会先调用用户指定的 new-handler
错误处理函数,之后才抛出异常。用户可以通过 set_new_handler
函数指定 new-handler
。
cpp
#include <iostream>
#include <new>
// 自定义 new-handler 函数
void outOfMem() {
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main() {
// 设置 new-handler
std::set_new_handler(outOfMem);
int *pBigDataArray = new int[100000000L];
// 其他代码...
return 0;
}
在上述代码中,如果 operator new
无法为 100000000
个整数分配空间,outOfMem
函数将被调用,程序会输出错误信息并中止。
2. 设计良好的 new-handler
应做的事情
一个设计良好的 new-handler
函数必须做到以下事情之一:
- 释放更多内存 :在程序启动时分配一大块内存,在
new-handler
第一次被调用时释放它供程序使用。 - 安装不同的
new-handler
:如果当前new-handler
无法使更多内存可用,可安装另一个new-handler
。 - 卸载
new-handler
:将空指针传给set_new_handler
,内存分配失败时operator new
抛出异常。 - 抛出异常 :抛出
bad_alloc
或其派生类型的异常。 - 不再返回 :典型情况是调用
abort
或exit
。
3. 类特定的 new-handler
实现
C++ 没有直接支持类特定的 new-handler
,但可以自己实现。通过为每个类提供 set_new_handler
和 operator new
的自定义版本来实现。
cpp
#include <iostream>
#include <new>
// 资源管理类,用于管理 new-handler
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}
~NewHandlerHolder() { std::set_new_handler(handler); }
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
// 支持类特定 new-handler 的模板基类
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 currentHandler;
};
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));
return ::operator new(size);
}
template <typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
// Widget 类,继承自 NewHandlerSupport
class Widget : public NewHandlerSupport<Widget> {
// 其他成员...
};
// 自定义 new-handler 函数
void outOfMem() {
std::cerr << "Widget memory allocation failed\n";
std::abort();
}
int main() {
// 设置 Widget 类的 new-handler
Widget::set_new_handler(outOfMem);
Widget *pw1 = new Widget;
// 其他代码...
return 0;
}
在这个例子中,NewHandlerSupport
是一个模板基类,Widget
类继承自 NewHandlerSupport<Widget>
,通过这种方式为 Widget
类提供了类特定的 new-handler
。
4. 奇特的递归模板模式(CRTP)
NewHandlerSupport
模板使用了奇特的递归模板模式(CRTP)。模板参数 T
仅用于区分不同的继承类,模板机制会为每个实例化的 NewHandlerSupport<T>
生成一个 currentHandler
的拷贝。
5. nothrow new
直到 1993 年,C++ 要求 operator new
分配失败时返回空指针。现在标准规定抛出 bad_alloc
异常,但为了兼容旧代码,提供了 nothrow
形式的 operator new
。
cpp
#include <iostream>
#include <new>
class Widget {
// 类定义...
};
int main() {
Widget *pw1 = new Widget; // 分配失败抛出 bad_alloc 异常
if (pw1 == 0) {
// 此测试必然失败
}
Widget *pw2 = new (std::nothrow) Widget; // 分配失败返回 0
if (pw2 == 0) {
// 此测试可能成功
}
return 0;
}
需要注意的是,nothrow new
只能保证 operator new
不会抛出异常,但后续的构造函数调用仍可能抛出异常。
总结要点
set_new_handler
的使用 :set_new_handler
允许指定一个在内存分配请求无法满足时调用的函数,通过自定义new-handler
函数可以处理内存分配失败的情况。- 类特定的
new-handler
:通过自定义set_new_handler
和operator new
以及使用 CRTP 模板基类,可以为每个类提供类特定的new-handler
。 nothrow new
的局限性 :nothrow new
仅适用于内存分配,后续的构造函数调用可能依然会抛出异常,其作用有限。