C++笔记(1):了解new-handler的行为

在C++里面,内存分配与释放是C++最具有魅力又不容易掌握的特色之一,其具体代码如下:

// 分配
int* i = new int;

// 释放
delete i;

但在使用new分配内存时,有可能会出现因内存空间不足而导致无法分配的情况,出现这种情况时,不同的C++编译器有两种处理方法:

在最老式的C++标准中,当new无法分配足够的内存时,将会返回null,但这种编译器现在几乎已经用不到了;目前的编译器对于这种情况则是会抛出std::bad_alloc异常,交由上层处理。但老式的处理方式也不是不能使用,通过声明“nothrow形式”,可以强行使用老式处理方法,代码如下:

// 声明使用nothrow,不推荐
int* i = new (std::nothrow) int;

但这种方法并不推荐,因为虽然new (std::nothrow) int调用的new并不抛出异常,但int构造函数却可能会。如果出现这种情况,该异常一样会传播出去,因此使用nothrow new只能保证new不抛出异常,但不保证像new (std::nothrow) int这样的表达式绝不抛异常。

但这不是重点。

当new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,即new-handler函数来进行一些处理。该函数无参数,无返回值,可以通过使用std::set_new_handler(new_handler p)来设置它,代码如下:

// new-handler函数的类型定义
typedef void (*new_handler) ();

// 设置new-handler的函数
new_handler std::set_new_handler(new_handler p) throw();

下面是使用的例子:

// new-handler函数
void PrintError()
{
	cout<<"new is Error!"<<endl;
}

int main()
{
	set_new_handler(PrintError);
	int* p = new int[0x7fffffff];
}

值得注意的是,当new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够的内存为止。

再进一步。

大多数时候,直接使用set_new_handler设置new-handler函数,来处理所有new分配失败的情况,并不符合自己的需求。而是希望能根据申请内存空间的类的不同,而赋予不同的new-handler,而对其它类,则使用默认的new-handler函数。例如:

// 失败则调用X的new-handler
X* p1 = new X;

// 失败则调用Y的new-handler
Y* p2 = new Y;

// 失败则使用默认new-handler
int* p3 = new int;

此时可以使用如下代码来解决:

using namespace std;

// 资源管理类
class NewHandleHolder{
public:
	explicit NewHandleHolder(new_handler nh):handle(nh){}
	~NewHandleHolder(){set_new_handler(handle);}
private:
	new_handler handle;
	// 私有化拷贝构造函数,赋值函数,以阻止拷贝
	NewHandleHolder(const NewHandleHolder&);
	NewHandleHolder& operator=(const NewHandleHolder&);
};

// 为特定类设定专属new_handler的模板类
template<typename T>
class NewHandleSupport{
public:
	static new_handler set_new_handler(new_handler p) throw();
	static void* operator new(size_t size) throw(bad_alloc);
	static void* operator new[](size_t size) throw(bad_alloc);
private:
	static new_handler currentHandle;
};

template<typename T>
new_handler NewHandleSupport<T>::set_new_handler(new_handler p) throw()
{
	new_handler OldHandle = currentHandle;
	currentHandle = p;
	return OldHandle;
}

template<typename T>
void* NewHandleSupport<T>::operator new(size_t size) throw(bad_alloc)
{
	NewHandleHolder h(::set_new_handler(currentHandle));
	return ::operator new(size);
}

template<typename T>
void* NewHandleSupport<T>::operator new[](size_t size) throw(bad_alloc)
{
	NewHandleHolder h(::set_new_handler(currentHandle));
	return ::operator new(size);
}

template<typename T>
new_handler NewHandleSupport<T>::currentHandle = 0;

// 测试类1,只需继承NewHandleSupport即可拥有专属new_handler
class Widget_1:public NewHandleSupport<Widget_1>{

};

// 测试类2,只需继承NewHandleSupport即可拥有专属new_handler
class Widget_2:public NewHandleSupport<Widget_2>{
	char c[0x7fffffff];
};

// 测试用函数1
void Widget_1_Error()
{
	cout<<"The Widget_1 class is Error!"<<endl;
}

// 测试用函数2
void Widget_2_Error()
{
	cout<<"The Widget_2 class is Error!"<<endl;
}

int main()
{
	// 测试类的使用
	Widget_1::set_new_handler(Widget_1_Error);
	Widget_2::set_new_handler(Widget_2_Error);

	// 分配成功
	int* i = new (nothrow) int;
	delete i;

	// 分配错误1
	Widget_1* w = new Widget_1[0x7fffffff];

	// 分配错误2
	Widget_2* w = new Widget_2;

	system("pause");
	return 0;
}

在该解决方法中,使用NewHandlerHolder类作为资源管理类,其功能仅作为保存new_handler变量,且在构造时保存,在析构时再将保存的new_handler还原回去。

而NewHandlerSupport则作为所有想要设置自己专属new-handler的类的基类而被使用。由于该类是模板,因此会根据不同的子类而实例化。它的功能是重载系统的set_new_handler、new和new[]函数,当实例化的类使用该三种函数时,执行自定义的操作。

发表评论

电子邮件地址不会被公开。 必填项已用*标注