c++ new内存分配失败后的new_handler
1. new_handler/ set_new_handler/get_new_handler
- c++使用new分配内存,当
operator new
无法满足某一内存分配需求时,它会抛出异常,但c++允许在抛出异常前调用一个自定义函数(new_handler
),用来给客户自定义指定内存分配不足这一行为的处理方式。 - 加入我们自定义函数需要调用接口
set_new_handler
,位于头文件<new>
, 接口形参也就是我们的自定义函数为一个函数指针,函数参数和返回值都是void, 接口返回值也是一个函数指针,返回前一个自定义行为 (用新的行为替换老的行为) get_new_handle
为c++11加入的,返回当前安装好的处理行为。
// 函数原型
typedef void (*new_handler)(); // 自定义处理函数类型
new_handler set_new_handler(new_handler) throw(); // 安装新行为,返回的是老行为
new_handler get_new_handler() noexcept; // 获取当前安装的行为
// eg:
class Base {
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
// 在构造函数的时候调用,默认是static的。
static void * operator new(size_t p)
{
cout << "operator new" << endl;
return ::operator new(p); // 调用全局的operator new
}
// 在构造函数的时候调用,默认是static的。
static void operator delete(void *p)
{
cout << "operator delete" << endl;
return ::operator delete(p); // 调用全局的operator delete
}
static void NoMemroy(); // new_handler行为函数
};
void Base::NoMemory()
{
printf("内存不够呀内存不够\n");
}
int main()
{
auto pppp = set_new_handler(Base::NoMemory); // 可以安装一个类成员函数作为新行为
auto ptr = get_new_handler(); // 获取当前安装的行为
ptr(); // 相当于调用 NoMemory()
auto ptr2 = set_new_handler(OutOfMemory); // 安装新行为OutOfMemory, 返回老行为NoMemory
ptr2();
// auto pb = new Base[333388336633](); // 内存分配不足,调用new_handler自定义处理函数 NoMemory
return 0;
}
2. 一个设计良好的new_handler必须做以下事情:
- 参考:effective C++ 条款 49:了解new-handler的行为
1.让更多内存可使用。造成operator new内的下一次内存分配动作可能成功。一个做法是,程序开始执行就分配一大块内存, 在new-handler第一次被调用,将它们释还给程序使用。
2.安装另一个new-handler。如果这个new-handler无法取得更多可用内存,或许它知道另外有个new-handler有此能力。目前这个就可以安装另外那个以替换自己(只需调用set_new_handler)。下次当operator new调用那个new-handler,调用的将是最新安装的那个。(这个旋律的变奏之一就是让new-handler修改自己的行为,于是当他下次被调用就会做些不同的事。为达此目的,做法之一就是令new-handler修改“会影响new-handler行为”的static数据、namespace数据、或global数据。)
3.卸除new-handler。就是将null指针传给set_new_handler。一旦没有安装任何new_handler,operator new会在内存分配不成功时抛出异常。
4.抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕获,因此会传到内存索求处。
5.不反回。通常调用abort或exit。
3. 类级别的new_handler行为
3.1 之前调用接口set_new_handler
来替换新行为的方式是针对全局new
范围的,如果想要对每个类调用new
分配内存失败后 做不同的行为处理, 需要为每个类都加上自定义处理方式,方法是为每个类重写 operator new
和 set_new_handler
- 知识点:
operator new
和set_new_handler
都是static
(new运算符在构造函数时调用,那时还没有对象及内存分配)static
成员变量必须在class
定义之外定义(除非它们是const而且是整数型)- 当
new_handler
被设置成nullptr
时,内存分配失败直接抛出bad_alloc
异常
#include<iostream>
#include <new>
using namespace std;
class Base {
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
// 重写operator new和 set_new_handler
static std::new_handler newHandler_; // 辅助 set_new_handler, 保存处理本类内存分配失败后的新行为
static std::new_handler set_new_handler(std::new_handler newHandler);
static void *operator new(std::size_t size) throw(std::bad_alloc); // operator new默认是static的
static void *operator new[](std::size_t size) throw(std::bad_alloc); // operator new[]默认是static的
};
std::new_handler Base::newHandler_ = nullptr; // 初始化类默认行为
std::new_handler Base::set_new_handler(std::new_handler newHandler) // 重写set_new_handler,给类安装new_handler
{
std::new_handler newHandler1 = newHandler_;
newHandler_ = newHandler; // 保存新行为
return newHandler1; // 返回老行为
}
void *Base::operator new(std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new" << endl;
auto ptr = ::set_new_handler(newHandler_);
::operator new(size); // 这里有个弊端,当分配异常的时候,不会再重新设置new_handler
::set_new_handler(ptr);
}
void *Base::operator new[](std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new[]" << endl;
auto ptr = ::set_new_handler(newHandler_);
::operator new[](size); // 这里有个弊端,当分配异常的时候,不会再重新设置new_handler
::set_new_handler(ptr);
}
void NoMemory()
{
cout << "NoMemory" << endl;
::set_new_handler(nullptr); // 当new_handler为nullptr时,分配内存时候后直接抛出异常bad_alloc
}
void NoMemoryGlobal()
{
cout << "NoMemoryGlobal" << endl;
::set_new_handler(nullptr); // 当new_handler为nullptr时,分配内存时候后直接抛出异常bad_alloc
}
int main()
{
set_new_handler(NoMemoryGlobal); // 安装全局NoMemoryGlobal
Base::set_new_handler(NoMemory); // 安装类的行为NoMemory
try {
Base *p = new Base[33333333333333]();
} catch (exception &e){
cout << e.what() << endl;
}
cout << "endl" << endl;
}
3.2 在类中重写operator new
和set_new_handler
行为可以解决类级别的new
内存失败处理,如果每个类都要处理,则需要对所有类重写,比较麻烦,可以将重写行为提取出来单独成一个类,通过继承方式来简化重写行为,不用再每个类中都重写。
- 提取重写部分
operator new
和set_new_handler
形成单独工具类 - 目标类继承这个工具类(不能包含这个工具类,因为重写动作是在工具类中,必须有继承关系才能实现目标类的重写动作)
// 抽取类
class BaseHandler {
public:
// 重写operator new和 set_new_handler
static std::new_handler newHandler_; // 辅助 set_new_handler, 保存处理本类内存分配失败后的新行为
static std::new_handler set_new_handler(std::new_handler newHandler);
static void *operator new(std::size_t size) throw(std::bad_alloc); // operator new默认是static的
static void *operator new[](std::size_t size) throw(std::bad_alloc); // operator new默认是static的
~BaseHandler()
{
newHandler_ = nullptr;
}
};
class Base : public BaseHandler {
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
};
class Base2 : public BaseHandler {
public:
Base2()
{
cout << "Base2()" << endl;
}
~Base2()
{
cout << "~Base2()" << endl;
}
};
std::new_handler BaseHandler::newHandler_ = nullptr; // 初始化类默认行为
std::new_handler BaseHandler::set_new_handler(std::new_handler newHandler) // 重写set_new_handler
{
std::new_handler newHandler1 = newHandler_;
newHandler_ = newHandler; // 保存新行为
return newHandler1; // 返回老行为
}
void *BaseHandler::operator new(std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new" << endl;
auto ptr = ::set_new_handler(newHandler_);
::operator new(size); // 这里有个弊端,当分配异常的时候,new_handler得不到置位
::set_new_handler(ptr);
}
void *BaseHandler::operator new[](std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new[]" << endl;
auto ptr = ::set_new_handler(newHandler_);
::operator new[](size); // 这里有个弊端,当分配异常的时候,new_handler得不到置位
::set_new_handler(ptr);
}
void NoMemory()
{
cout << "NoMemory" << endl;
::set_new_handler(nullptr); // 当new_handler为nullptr时,分配内存时候后直接抛出异常bad_alloc
}
void NoMemoryGlobal()
{
cout << "NoMemoryGlobal" << endl;
::set_new_handler(nullptr); // 当new_handler为nullptr时,分配内存时候后直接抛出异常bad_alloc
}
int main()
{
Base2::set_new_handler(NoMemoryGlobal); // 安装NoMemoryGlobal
try {
Base2 *p = new Base2[33333333333333]();
} catch (exception &e){
cout << e.what() << endl;
}
//Base::set_new_handler(NoMemory); // 安装类的行为NoMemory
// 这里Base并没有安装新行为,会使用Base2的行为
try {
Base *p = new Base[33333333333333]();
} catch (exception &e){
cout << e.what() << endl;
}
cout << "endl" << endl;
}
// 输出
Base::operator new[]
NoMemoryGlobal
std::bad_alloc
Base::operator new[]
NoMemoryGlobal
std::bad_alloc
endl
- 上面实现了对重载部分的封装,需要自定义行为的类继承工具类
BaseHandler
- 弊端:类
A
继承BaseHandler
后,设置了自定义行为action1
,内存分配失败后,类B
继承BaseHandler
时,不添加自己的行为也会默认执行action1
. 这是因为类A
在执行内存分配失败后,直接抛出异常,但是new_handler
没有被置回去,这个时候为了能在异常情况使new_handler
能还原,使用RAII
方式管理new_handler
// 新增RAII管理
template <typename T>
class HandlerResAlloc {
public:
new_handler _newHandler1;
HandlerResAlloc(new_handler newHandler1) : _newHandler1(newHandler1) {};
~HandlerResAlloc ()
{
T::set_new_handler(_newHandler1); // 需要指定类型基类中的值为原始值
}
};
void *BaseHandler::operator new(std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new" << endl;
HandlerResAlloc<BaseHandler> handlerResAlloc(::set_new_handler(newHandler_));
::operator new(size);
}
void *BaseHandler::operator new[](std::size_t size) throw(std::bad_alloc) // 重写operator new
{
cout << "Base::operator new[]" << endl;
HandlerResAlloc<BaseHandler> handlerResAlloc(::set_new_handler(newHandler_));
::operator new[](size);
}
int main()
{
Base2::set_new_handler(NoMemoryGlobal); // 安装NoMemoryGlobal
try {
Base2 *p = new Base2[33333333333333]();
} catch (exception &e){
cout << e.what() << endl;
}
// Base 没有指定新行为,使用默认行为
try {
Base *p = new Base[33333333333333]();
} catch (exception &e){
cout << e.what() << endl;
}
cout << "endl" << endl;
}
// 输出
Base::operator new[]
NoMemoryGlobal
std::bad_alloc
Base::operator new[]
std::bad_alloc
endl
3.3 提取类总结:
- 基类重写
operator new
和set_new_handler
,set_new_handler
是用来给类安装自定义handler
的(只是将内中的handler
置位自定义值),operator new
调用全局set_new_handler
设置自定义handler
,同时将返回值用RAII
管理, 再调用全局operator new
;不管全局operator new
是否成功,RAII
在调用类的set_new_handler
将类中的handler
复位。 - 全局的
handler
由自定义处理函数来负责复位,如果自定义函数不负责,可以在RAII
析构是将类的handler
和全局handler
同时复位 - 两个辅助类: 基类(负责动作
new operator
,set_new_handler
) +RAII
类(管理类的handler
)
总结:
set_new_handler
允许调用者指定一个函数,在无法满足内存分配时使用。