c++ new内存分配失败后的new_handler

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 newset_new_handler

  • 知识点:
    • operator newset_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 newset_new_handler行为可以解决类级别的new内存失败处理,如果每个类都要处理,则需要对所有类重写,比较麻烦,可以将重写行为提取出来单独成一个类,通过继承方式来简化重写行为,不用再每个类中都重写。

  1. 提取重写部分operator newset_new_handler形成单独工具类
  2. 目标类继承这个工具类(不能包含这个工具类,因为重写动作是在工具类中,必须有继承关系才能实现目标类的重写动作)
// 抽取类
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 newset_new_handlerset_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 operatorset_new_handler) + RAII类(管理类的handler

总结:

  • set_new_handler允许调用者指定一个函数,在无法满足内存分配时使用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值