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

(一)

new_handler函数:当operator new或operator new[]分配内存失败时调用的函数。

set_new_handler函数:试图分配更多内存以足够使用,成功时会返回,否则会抛出一个bad_alloc异常(或其派生类对象)或调用cstdlib的abort()或exit()函数强制退出。


(二)

当operator new分配内存失败(不足)时,会先调用一个客户指定的错误处理函数new-handler,然后抛出一个异常(旧式的行为是返回一个空指针)。客户使用set_new_handler来设定该行为,它是声明于<new>中的一个标准程序库函数:

namespace std{
<span style="white-space:pre">	</span>typedef void(*new_handler)();//指向函数的指针
<span style="white-space:pre">	</span>new_handler set_new_handler(new_handler p) throw();//获得分配内存失败时应该被调用的函数指针,并返回本函数在调用前正在执行(但马上就要被替换)的new-handler函数指针
}

当operator new无法满足内存申请时,会不断调用new-handler函数直到内存够用。
注:operator new诸函数与new不同,前者是标准程序库中的实现,它是一个函数,但不是重载,而后者是一个表达式。


(三)

一个设计良好的new-handler函数必须做以下事情:

  • 让更多内存可以被使用。一个做法是程序一开始就分配大量内存,当new-handler被调用时将这些内存空间归还给程序使用。
  • 安装另一个new-handler。如果当前的new-handler函数不能申请足够内存,则可以使用set_new_handler替换为其他的合适版本。
  • 卸除new-handler,即将空指针传递给set_new_handler。一旦没有安装任何new-handler,operator new会在内在分配失败时抛出异常。
  • 抛出bad_alloc(或其派生类对象)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存申请处。
  • 不返回,通常调用abort()或exit()。

(四)

为支持类专属的new-handler,可以在类中声明需要的new_handler函数,并提供类专属的set_new_handler(指定new-handler)和operator new(确保在分配类对象内存的过程中以该类专属的new-handler替换全局的new-handler)版本。具体过程为:

(1)声明一个类型为new_handler的静态成员函数,用以指向该类的new-handler。例如:

class Widget {
public:
	static new_handler set_new_handler(new_handler p) throw();
	static void* operator new(size_t size) throw(bad_alloc);
private:
	static new_handler currentHandler;
};

注意static成员必须在类外定义(除非是const且是int型),所以需要这么写:

std::new_handler Widget::currentHandler = 0;

(2)set_new_handler函数将它获得的指针存储起来,然后返回调用前存储的指针(与标准set_new_handler)相同:
std::new_handler Widget::set_new_handler(std::new_handler p) throw() {
	std::new_handler oldHandler = currentHandler;
	currendHandler = p;
	return oldHandler;
}

(3)使该类的operator new做以下事情:
  • 调用标准set_new_handler,告诉该类的错误处理函数,这会将该类的new-handler安装为全局的new-handler。
  • 调用全局的operator new,执行实际的内存分配。如果分配失败,全局的operator new会调用该类的new-handler,因为那个函数刚刚被安装为全局的new-handler。如果全局的new-handler最终无法分配足够内存,会抛出bad_alloc异常,在此情况下该类的operator new必须恢复原来的全局new-handler,然后再传播该异常。为确保原来的new-handler总是能够被重新安装,该类将全局new-handler视为“资源”并使用资源管理对象来防止资源泄漏。
  • 如果全局operator new能够分配足够的内存,会返回一个指针指向分配的地址。该类的析构函数会管理全局new-handler,它会自动将该类operator new被调用前的那个全局new-handler恢复回来。

(五)
实例:从资源处理类开始,只有基础性的RAII操作,在构造过程中获得一笔资源,并在析构过程中归还:
class NewHandlerHolder {
public:
	explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}  //取得目前的new-handler
	~NewHandlerHolder() {
		set_new_handler(handler);
	}  //释放new-handler
private:
	std::new_handler handler;   //保存new-handler
	NewHandlerHolder(const NewHandlerHolder&);   //阻止复制行为
	NewHandlerHolder& operator= (const NewHandlerHolder&);
};
假设有一个类Widget,它实现operator new的方法:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc) {
	NewHandlerHolder h(std::set_new_Handler(currentHandler));  //安装Widget的new-handler
	return ::operator new(size);  //分配内存或抛出异常,恢复全局new-handler
}
Widget的客户应该类似如下方式new-handler:
void outOfMem();	//声明内存分配失败时的处理函数
Widget::set_new_handler(outOfMem);	//将上面的函数设定为Widget的new-handler函数
Widget* pw1 = new Widget;	//如果内存分配失败,调用outOfMem()
std::string* ps = new std::string;//如果内存分配失败,调用全局的new-handler函数(如果有的话)
Widget::set_new_handler(0);	//设定Widget专属的new-handler为空,失去专属版本
Widget* pw2 = new Widget;	//如果内存分配失败,则抛出异常

(六)

可以将上述方法抽象,建立mixin风格的基类,它允许派生类继承单一特定能力(这里是“设定该类专属的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 NewHandlerHolder<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);
}
因为对于class里面的static成员变量的初始化必须要在class的外面定义。

所以以下是将每一个currentHandler初始化为null

template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

(七)

对于一个具体的类,添加专属的set_new_handler就很容易了:继承。
class Widget : public NewHandlerSupport<Widget>{
	...	//和先前一样,但不必声明set_new_handler或operator new
};

(八)

6.旧式的operator new在内存分配失败后返回null,为了兼容旧代码,C++提供了另
一形式的operator new,称为nothrow形式,在new使用的场合使用了nothrow对象(定义于<new>头文件中)。建议不要使用。因为如果构造函数也抛出异常的话,那样的话会产生递归发生异常。

请记住:

(1)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

(2)Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是有可能抛出异常的。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值