您的位置:首页 > 编程语言 > C语言/C++

【effective c++读书笔记】【第8章】定制new和delete(1)

2015-08-18 15:01 453 查看
条款49:了解new-handler的行为

1、当operator new无法满足某一内存分配需求时,它会抛出异常,以前会返回null指针。当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,它是声明于<new>的一个标准程序库函数:

namespace std {
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();  //异常声明,表示该函数不抛出任何异常
}


set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是一个指针,指向set_new_handler被调用前正在执行的(但马上就要被替换)的那个new-handler函数。

set_new_handler用法例子:

#include<new>
#include<iostream>
using namespace std;

void outOfMem(){
cerr << "Unable to satisfy request for memory\n";
abort();
}

int main(){
set_new_handler(outOfMem);
int* pBigDataArray = new int[0x7fffffff/4];

system("pause");
return 0;
}


运行结果:



2、当operator new无法满足内存申请时,会不断调用new-handler函数,直到找到足够内存。一个设计良好的new-handler函数必须做以下事情:

a、让更多内存可被使用。一个做法是程序一开始就分配一大块内存,当new-handler第一次被调用,将它们释还给程序使用。

b、安装另一个new-handler。如果目前这个new-handler函数无法取得更多可用内存,则可以安装另外那个new-handler替换自己(只要调用set_new_handler)。

c、卸载new-handler,也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内在分配失败时抛出异常。

d、捕捉bad_alloc(或派生自bad_alloc)的异常。

e、不返回,通常调用abort或exit。

3、C++并不支持类专属之new-handlers,但我们可以自己实现出这种行为。只需令每一个类提供自己的set_new_handler和operator new即可。set_new_handler使客户可以指定类专属的new-handler(就像标准的set_new_handler允许客户指定global new-handler),operator new则确保在分配类对象内存的过程中以类专属之new-handler替换global 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:
//声明一个类型为new_handler的静态成员函数,用以指向该类的new-handler
static std::new_handler currentHandler;
};

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

std::new_handler Widget::currentHandler = 0;

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;
}

最后,Widget的operator new做以下事情:

a、调用标准set_new_handler,告诉该类的错误处理函数,这会将该类的new-handler安装为全局的new-handler。

b、调用全局的operator new,执行实际的内存分配。如果分配失败,全局的operatornew会调用该类的new-handler,因为那个函数刚刚被安装为全局的new-handler。如果全局的new-handler最终无法分配足够内存,会抛出bad_alloc异常,在此情况下该类的operator new必须恢复原来的全局new-handler,然后再传播该异常。为确保原来的new-handler总是能够被重新安装,该类将全局new-handler视为“资源”并使用资源管理对象来防止资源泄漏。

c、如果全局operator new能够分配足够的内存,会返回一个指针指向分配的地址。该类的析构函数会管理全局new-handler,它会自动将该类operator new被调用前的那个全局new-handler恢复回来。

4、构造一个资源处理类来操作new-handler,那里只有基础性RAII操作,在构造过程中获得资源,在析构过程中释放:

class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}  //取得目前的new-handler
~NewHandlerHolder() {//释放new-handler
set_new_handler(handler);
}
private:
std::new_handler handler;   //记录下来
NewHandlerHolder(const NewHandlerHolder&);   //阻止拷贝行为
NewHandlerHolder& operator= (const NewHandlerHolder&);
};

这使得类Widge的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;	//如果内存分配失败,则抛出异常

5、可以将4中例子加以复用,建立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);
}

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

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

有了上述模板类,为Widget添加添加专属的set_new_handler就轻而易举了:只要令Widget继承自NewHandlerSupport<Widget>就行。

class Widget : public NewHandlerSupport<Widget>{
...	//和先前一样,但不必声明set_new_handler或operator new
};

6、因为之前的operator new在无法分配足够内存时会返回null。为了兼容,提供了另一种形式的operator new, 负责应传统的“分配失败时返回null”的行为,称为nothrow形式,因为他们在new的使用场合使用了nothrow对象。如下:

class Widget { ... };
Widget* pw1 = new Widget;  //如果分配失败抛出bad_alloc
if (pw1 == 0) ... //这个测试一定失败
Widget* pw2 = new (std::nothrow) Widget; // 如果分配失败,返回0
if (pw2 == 0) ... //这个测试可能成功

Nothrow new对异常的强制保证性并不高。

请记住:

set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: