new-handler用法

文章详细介绍了C++中new操作符在内存分配不足时如何使用new_handler函数进行处理,包括如何设置和自定义new_handler,以及nothrow运算符的作用。new_handler允许在内存不足时执行用户定义的操作,如尝试释放更多内存或抛出异常。nothrow则提供了一种在分配失败时返回NULL的方式,但不适用于构造函数中其他内存分配的情况。
摘要由CSDN通过智能技术生成

最近看代码看到nothrow,突然想到effective c++上把new-handler与nothrow放在一起,并且new-handler的用法很“别致”,于是在这里做一下笔记。
当operator new分配空间不够时,除了抛出异常,还会调用new_handler函数,准确来说是先调用new_handler函数,再抛出异常,new_handler函数由std::set_new_handler函数定义,new_handler函数可以报错,也可以为new分配更大的空间
std::set_new_handler函数用于为new指定new_handler函数,参数是函数指针,使用时往里面传函数指针就行。一般用法如下:

void outOfMem()
{
	std::cerr <<"Unable to satisfy request for memory\n";
	std::abort();
}
int main()
{
	std::set_new_handler(outOfMem);
	int *pBigDataArray = new int[1000000000L];
	...
}

以上所示,如果无法分配空间则会调用outOfMem函数。
设计合理的new_handler函数应该满足以下几点:
1.让更多内存可被使用
2.**如果自己无法分配更多的空间,则安装另一个new_handler函数以供空间的分配。**这点犹如STL中的适配器,优秀的内存空间分配工具总会想尽办法来分配更多的空间。
3.卸载new-handler。如果没有可用的new-handler,就将参数new-handler置为null(0),也是new_handler的初始值,使异常抛出。只有在new-handler为空的情况下,operator new才抛出异常。如果说用户没有设定new_handler函数,或者设定new_handler函数为0、null,则当内存分配不足时立即抛出异常。这样也可以理解为啥我们写代码什么都不做,内存空间不足时会直接报错了,挺好用的,所以当我们修改new_handler函数时也要遵循这一点,不可能程序内存不足还在跑而不报错,这是很恐怖的
4.抛出bad_alloc异常,这块没看懂,大概是因为C++新规定内存分配不足必须要抛出异常,而不是像以前返回NULL就行;
5.不返回,通常调用abort或exit,很遗憾,这边也没看懂,大概是因为想只抛出异常不返回吧。

之前说的std::set_new_handler是为全局提供new_handler函数,如果想为每个类提供特定的new_handler则让类自己设计set_new_handler和operator new函数即可。但要注意的是,标准的set_new_handler函数除了设置新的new_handler函数,还会返回旧的new_handler函数指针,因为新设定的new_handler函数可能知识某个类需要的,但是该类修改的是全局new_handler函数,用完完后当然要恢复旧的new_handler函数(注意这个与new_handler函数本身提供的卸载功能不同,这个是使用完new_handler函数后恢复到旧的new_handler函数)。所以设计类自己的set_new_handler函数也要遵循这一点

class Widget {

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;

};

std::new_handler Widget::currentHandler = 0;

std::new_handler Widget::set_new_handler(std::new_handler p) throw()

{

std::new_handler oldHandler = currentHandler;

currentHandler = p;

return oldHandler;

}

以上就是以Widget类为例子重写set_new_handler函数,当然是书上的经典代码,如果我能写出来就好了,哈哈哈

void * Widget::operator new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
}

以上是Widget类重写的operator new函数,就是我们常常调用的new,其中用到了一个NewHandlerHolder类,这个类设计的非常巧妙,可以保存旧的new_handler函数指针,为了Widget类能恢复原本的global new-handler,就是new-handler的初始值null,然后能顺利抛出异常。

class NewHandlerHolder{
public:
    explicit NewHandlerHolder(std::new_handler nh) :handler(nh){};
    ~NewHandlerHolder() {std::new_handler (handler);}
private:
    std::new_handler  handler;
    NewHandlerHolder(const NewHandlerHolder &);
    NewHandlerHolder& operator=(const NewHandlerHolder);
};

结合重写的new函数可以看出,new中的set_new_handler函数返回旧的new_handler函数指针,该函数指针作为NewHandlerHolder类构造函数的初始值,并且被记录在handler变量中。当用NewHandlerHolder类创建对象,对象消亡时会自动调用析构函数,析构函数中会将new_handler函数再设定为旧的new_handler函数(变量handler记录的是旧的new_handler函数指针)。

void outOfMem();
Widget::set_new_handler(outOfMem);

Widget* pm1 = new Widget;  //如果内存分配失败,将调用outOfMem

Widget::set_new_handler(0);
Widget* pm1 = new Widget;    //如果内存分配失败, 将抛出异常

按照以上例子走一遍,首先Widget调用自己的set_new_handler函数,将Widget中的静态变量currentHandler设置为outOfMem,第二步Widget调用new函数时,调用std::set_new_handler设置new_handler为currentHandler,并且保留旧的std::new_handler,应该是null,如果outOfMem起作用则被调用,如果不起作用则恢复new_handler为null,抛出异常。
当然如果每一个类都要像这样声明set_new_handler和operator new用户会累死的,于是有一种很好的方法,把这种模式写成固定的类模板,用户只要提供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);
    // ...   其他的operator new版本
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);
}

//将每一个currentHandler 初始化为null
template<typename T>
std::new_handler  NewHandlerSupport<T>::currentHandler = 0;

使用时继承就行,如下所示:

class Widget : public NewHandlerSupport<Widget>{
    
};

以上代码还没细看,等到需要用时再细细研读。

除了以上方式解决内存不够的问题,还有一种就是nothrow,nothrow可以实现分配失败便返回null的行为。

Widget *pw2 = new (std::nothrow) Widget;

这种方式是可行,但相比于new_handler有缺陷,因为new中分为几个步骤,一个是分配空间,接着是调用Widget的构造函数,nothrow只负责在分配空间时报错返回null,如果在Widget构造函数时又new一些内存,nothrow就没办法了,只能抛出异常,而不能做到“分配失败就返回null”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值