49:了解 new-handler 行为
文章目录
operator new 失败后的行为
当 operator new 无法满足某一内存分配需求时,它会抛出异常。旧式编译器会返回 null 指针。
而抛出异常之前,它会先调用一个客户指定的错误处理函数: new-handler
。
为了指定这个“用以处理内存不足”的函数,客户必须调用 set_new_handler
,那是声明于 的一个标准程序库函数:
namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
} // namespace std
// Example
void OutOfMem() {
std::cerr << "Unable to satisfy request for memory" << std::endl;
std::abort();
}
int main() {
std::set_new_handler(OutOfMem);
int *pBigDataArray = new int[10000000000000000L];
pBigDataArray[0] = 213;
delete[] pBigDataArray;
pBigDataArray = nullptr;
return 0;
}
// 结果:
Unable to satisfy request for memory
Aborted (core dumped)
new-handler 是一个 typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。
指向一个无参数值无返回值的函数。我们可以通过 set_new_handler 函数去指定客户想要的 new-handler。
set_new_handler 函数获得一个 new-handler 并返回一个new-handler 函数。
set_new_handle 声明式尾部的 throw() 是一份异常明细,表明该函数不抛出任何异常。
当 operater new 无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存。
设计 new-handler
一个设计良好的 new-handler 函数必须考虑以下几点:
- **提供更多的可被使用的内存。**这会造成下次在operator new内下一次分配动作可能成功。实现这个策略的一个做法是在程序的开始阶段分配一大块内存,然后在第一次调用new-handler的时候释换给程序。
- 安装另一个的new-handler。如果当前的new-handler 无法取得更多可用内存,或许它知道另外哪个 new-handler 有此能力。做法之一是让new_handler修改会影响new-handler行为的static数据,namespace数据或 global 数据。
- 卸载new-handler,也就是向set_new_handler传递null指针。如果没有安装new-handler,operator new 会在内存分配不成功的时候会抛出异常;
- 抛出 bad-alloc(或派生自 bad-alloc )的异常;
- 不返回,调用abort或者exit。
设计 class 专属 new-handlers
Method 1: 为 class 提供特定的 set_new_handle 和 operator new
C++ 并不支持为每一个 class 提供专属版本的 new_handler,可以为 class 提供自己实现的 set_new_handler 函数 和 operator new 函数。
- 对于 set_new_handler ,我们根据参照默认实现即可
class Randy {
private:
static std::new_handler currentHandler_;
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);
~Randy(){};
};
std::new_handler Randy::Set_new_handler(std::new_handler p) throw() {
std::new_handler oldHandler = currentHandler_;
currentHandler_ = p;
return oldHandler;
}
// Static 成员必须在class 定义式之外被定义(除非它们是const 且是整数型)
// 在class 实现文件内初始化为0
std::new_handler Randy::currentHandler_ = 0;
Randy的 operator new 做以下事情:
- 调用标准版 set_new_handler ,告知 Randy 错误处理函数;
- 调用 global operator new,执行实际之内存分配;
- 如果 global operator new 能够分配足够1个Randy 对象所用内存, Randy的 operator new 会返回1个指针,指向分配所得。 Randy 的析构函数会管理 global new-handle,会自动将 operator new 被调用前的那个 global new-handler 恢复回来。
可以使用 RAII 管理 new_handler
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} // 取得目前的 new-handler
~NewHandlerHolder() { std::set_new_handler(handler); } // 释放 new-handler
private:
std::new_handler handler; // 记录下来
NewHandlerHolder(const NewHandlerHolder&); // 阻止 copying
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
class Randy {
private:
static std::new_handler currentHandler_;
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) {
// 安装 Randy 的new-handler
NewHandlerHolder h(std::set_new_handler(currentHandler_));
return ::operator new(size); // 分配内存或抛出异常。
}; // 恢复 global new-handler
~Randy(){};
};
std::new_handler Randy::Set_new_handler(std::new_handler p) throw() {
std::new_handler oldHandler = currentHandler_;
currentHandler_ = p;
return oldHandler;
}
std::new_handler Randy::currentHandler_ = 0;
void OutOfMem(); // 函数声明。此函数在 Randy 对象分配失败时被调用。
int main() {
Randy::Set_new_handler(OutOfMem); // 设定 OutOfMem 为 Randy 的new-handing 函数
Randy* pr1 = new Randy; // 如果内存分配失败,调用 OutOfMem
Randy::Set_new_handler(0); // 设定 Randy 专属 的new-handling 函数为null
return 0;
}
Method 2:设计 template 版本 new_handler
一个更好的方式是使用 template 进行模板编程,确保每一个derived class 获得一个实体互异的currentHandler成员变量,即根据不同 class 进行特化和具现化
。完整实现如下:
template <typename T>
class NewHandlerSupport {
public:
static std::new_handler set_new_handler(std::new_handler p) throw() {
const std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
void* operator new(std::size_t Size) throw(std::bad_alloc) {
NewHandlerHolder(std::set_new_handler(currentHandler));
return ::operator new(Size);
}
private:
static std::new_handler currentHandler;
};
// 以下将每一个 currentHandler 初始化为 null
template <typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
class Randy: public NewHandlerSupport<Randy> {
... // 不必声明 set_new_handler 和 operator new
};
为什么需要 template?
你可能注意到 NewHandlerSupport template 从未使用其类型参数T。
实际上,T 的确不被需要,我们只是希望,继承自 NewHandlerSupport 的 每一个class,拥有实体互异的 NewHandlerSupport 复件(其currentHandler 成员)。
类型参数只是用来区分不同的派生类,Template 机制会自动为每一个 T 生成一份 CurrentHandler 成员。
请记住
- set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。
- Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。