当operator new无法满足某一内存分配需求时,它会抛出异常。以前它会返回一个null指针。
当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler(其实operator new真正做的事情要稍微复杂些)。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于<new>的一个标准程序库函数:
namespace std {
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。set_new_handler声明式尾端的"throw()"是一份异常明细,表示该函数不抛出任何异常。
set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个函数,指向set_new_handler被调用前正在执行(但马上就要被替换)的那个new-handler函数。
可以这样使用set_new_handler:
//以下是当operator new无法分配足够内存时,该被调用的函数
void outOfMem()
{
cout << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
int* pBigDataarray = new int[100000000L];
return 0;
}
就本例而言,若operator new无法为100000000个整数分配足够空间,outOfMem会被调用,于是程序在发出一个信息之后夭折。
当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存。
一个设计良好的new-handler函数必须做以下事情:
1.让更多内存可被使用。
这便造成operator new内的下一次内存分配动作可能成功。
实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释还给程序使用。
2.安装另一个new-handler。
若目前这个new-handler无法取得更多可用内存,或许它知道另外哪个new-handler由此能力。若真如此,目前这个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。
这些选择让你在实现new-handler函数时拥有很大弹性。
有时候,你或许希望以不同的方式处理内存分配失败情况,你希望被分配物属于哪个class而定:
class X {
public:
static void outOfMemory();
//...
};
class Y {
public:
static void outOfMemory();
//...
};
//若分配不成功,调用X::outOfMemory
X* p1 = new X;
//若分配不成功,调用Y::outOfMemory
Y* p2 = new Y;
C++并不支持class专属的new-handler,但也并不需要。
你可以自己实现出这种行为。只需令每一个class提供自己的set_new_handle和operator new即可。其中set_new_handler使客户得以指定class专属的new-handler(就像标准的set_new_handler允许客户指定global new-handler),至于operator new则确保在分配class对象内存的过程中以class专属的new-handler替换global new-handler。
现在,假设你打算处理Widget class的内存分配失败情况。首先你必须登陆“当operator new无法为一个Widget对象分配足够内存时”调用的函数,所以你需要声明一个类型为new_handler的static成员,用以指向class Widget的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;
};
static成员必须在class定义式之外被定义(除非它们是const而且是整数型),所以需要这么写:
//在class实现文件内初始化为null
std::new_handler Widget::currentHandler = 0;
Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回先前(在此调用之前)存储的指针,这也正是标准版set_new_handler的作为:
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
最后,Widget的operator new做以下事情:
1.调用标准set_new_handler,告知Widget的错误处理函数。
这会将Widget的new-handler安装为global new-handler。
2.调用global operator new,执行实际的内存分配。
若分配失败,global operator new会调用Widget的new-handler,因为那个函数才刚被安装为global new-handler。若global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。在此情况下,Widget的operator new必须恢复原本的global new-handler,然后再传播该异常。为确保原本的new-handler总是能够被重新安装回去,Widget运用资源管理对象防止资源泄漏。
3.若global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,指向分配所得。Widget析构函数会管理global new-handler,它会自动将Widget的operator new被调用前的那个global new-handler恢复回来。
下面以C++代码再阐述一次:
class NewHandlerHolder {
public:
//取得目前的new-handler
explicit NewHandlerHolder(std::new_handler nh): handler(nh){}
~NewHandlerHolder() { std::set_new_handler(handler); }//释放它
private:
std::new_handler handler;//记录下来
NewHandlerHolder(const NewHandlerHolder&);//阻止copying
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
这就使得Widget的operator new的实现相当简单:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
//安装Widget的new-handler
NewHandlerHolder h(std::set_new_handler(currentHandler));
//分配内存或抛出异常,恢复global new-handler
return ::operator new(size);
}
Widget的客户应该类似这样使用其new-handlering:
//函数声明,此函数在Widget对象分配失败时调用
void outOfMem();
//设定outOfMem为Widget的new-handling函数
Widget::set_new_handler(outOfMem);
//若内存分配失败,调用outOfMem
Widget* pw1 = new Widget;
//若内存分配失败,调用global new-handing函数(若有的话)
std::string* ps = new std::string;
//设定Widget专属的new-handling函数为null
Widget::set_new_handler(0);
//若内存分配失败,立刻抛出异常
Widget* pw2 = new Widget;//class Widget并没有专属的new-handling函数
实现这一方案的代码并不因class的不同而不同,因此在它处加以复用是个合理的构想。一个简单的做法是建立起一个"mixin"风格的base class,这种base class用来允许derived class继承单一特定能力,在本例中是“设定class专属的new-handler”的能力。然后将这个base class转换为template,如此一来每个derived class将获得实体互异的class data复件。
这个设计的base class部分让derived class继承它们所需的set_new_handler和operator new,而template部分则确保每一个derived class获得一个实体互异的currentHandler成员变量。
与前个版本相比,唯一真正意义上的不同是,它现在可被任何有所需要的class使用:
//"mixin"风格的base class,用以支持class专属的set_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) 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 ::new_handler(size);
}
//以下将每一个currentHandler初始化为null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
有了这个class template,为Widget添加set_new_handler支持能力就轻而易举了:只要令Widget继承自NewHandlerSupport<Widget>就好。
像下面这样:
class Widget : public NewHandlerSupport<Widget> {
//...//和先前一样,但不必声明set_new_handler或operator new
};
这就是Widget为了提供“class专属的set_new_handler:所需做的全部动作。
总结
1.set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
2.Nothrow new是个颇为局限的工具,因为它只适用于内存分配,后继的构造函数调用还是可能抛出异常。