一. 内容
-
当你调用 operator new 函数,程序无法满足某一内存需求时,它会抛出异常。老旧的编译器会返回 null 指针。而抛出异常之前,程序会先调用一个 operator new 错误处理函数,名叫
new-handler
。new-handler 是一个 typedef,指向一个无参数值无返回值的函数
。我们可以通过 set_new_handler 函数去指定客户想要的 new-handler。set_new_handler 函数接受一个新的 new-handler 参数,返回被替换掉的 new-handler 函数
。 -
一个设计良好的 new-handler 函数必须考虑以下事情:
- 让更多内存被使用。一个实现策略是程序一开始就分配大量内存,当 new-handler 被调用时再返还给程序使用。
- 安装另一个 new-handler。如果存在其他 new-handler 可以提供内存,那么可以调用其他的 new-handler。
- 卸除 new-handler。当无法解决问题时,使用 null 指针赋予 set_new_handler 函数,operator new 会在内存分配不成功时抛出异常。
- 抛出 bad-alloc,或派生自 bad-alloc 的异常。
- 不返回,调用 abort() 或 exit()。
-
有时候你或许希望以不同的方式处理内存分配的情况,比如按不同的 class 进行处理,但是 C++ 并不支持为每一个 class 提供专属版本的 new_handler,好在我们可以模仿这一行为,只要我们
为 class 实现自己的 set_new_handler 函数 和 operator new 函数
即可。-
对于 set_new_handler ,我们根据参照默认实现即可:
static std::new_handler SetNewHandler(std::new_handler NewHandler) throw() { const std::new_handler OldHandler=std::set_new_handler(NewHandler); CurrentHandler=OldHandler; return OldHandler; }
-
对于 operator new,我们要做以下事情。
- 调用标准版 set_new_handler 安装我们自定义的 new-handler,将返回的标准版 new-handler 保存起来。
- 调用标准版 operator new。如果标准版 operator new 异常,那么会调用我们自定义的 new-handler 处理函数。
- 调用标准版 set_new_handler 重新安装标准版的 new-handler。
为了确保可以重新安装标准版 new-handler,我们可以采用条款13所说
以对象管理资源
的方法:class NewController { public: explicit NewController(std::new_handler InHandler): Handler(InHandler) {} ~NewController() { std::set_new_handler(Handler); } private: std::new_handler Handler; };
所以 operator new 实现如下:
void* operator new(std::size_t Size) throw(std::bad_alloc) { NewController(std::set_new_handler(CurrentHandler)); return ::operator new(Size); }
-
-
但是上述代码还是不够简洁,每一个 class 都要自己实现一个 set_new_handler 和 operator new 版本。一个更好的方式是
使用 template 进行模板编程,然后根据不同 class 进行特化和具现化
。完整实现如下:template <typename T> class NewHandlerSupport { public: static std::new_handler SetNewHandler(std::new_handler NewHandler) throw() { const std::new_handler OldHandler = std::set_new_handler(NewHandler); CurrentHandler = OldHandler; return OldHandler; } void* operator new(std::size_t Size) throw(std::bad_alloc) { NewController(std::set_new_handler(CurrentHandler)); return ::operator new(Size); } private: static std::new_handler CurrentHandler; }; template <typename T> std::new_handler NewHandlerSupport<T>::CurrentHandler = nullptr; class FDemo:public NewHandlerSupport<FDemo> { }; inline void TryWithNew() { FDemo::SetNewHandler([]() { std::cout<<"内存不够啦"<<"\n"; }); FDemo *Demos=new FDemo[1000123123100000](); }
注意,
当 operator new 无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存或异常退出
。当然,你想说为什么我们需要 template?我们似乎并没有使用到模板参数,是的,T 的确不被需要,我们只是希望,继承自 NewHandlerSupport 的 class 拥有各自的 CurrentHandler 成员。类型参数只是用来区分不同的派生类,然后
template 机制会自动为每一个 T 具现化一份 CurrentHandler 成员,即使它是 static 的
。也许你的焦虑还来自于 template class 导致的多重继承,可以先看看条款40。
-
早些版本的 C++ 要求 operator new 函数在无法分配足够内存时返回 nullptr 指针,现在新一代的 C++ 要求抛出 bad_alloc 异常。为了兼容,C++ 提供了另一种形式负责传统的行为:
new(std::nothrow)xxx;
保证 new 分配失败后返回 nullptr 指针。但即使 new 不抛出异常,紧接着的
xxx 构造函数的调用也有可能抛出异常
。异常行为依旧会产生,所以几乎没有运用 nothrow 的必要。
二. 总结
- set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。
- Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。