定制new和delete
了解new-handler的行为
- set_new_handler
当一个
operator new
抛出异常以反映一个未获满足的内存需求之前,他会首先调用一个客户指定的错误处理函数,一个所谓的new handler。为了指定这个用于处理内存不足的函数,客户必须调用set_new_handler
,那是一个声明于<new>
的标准库函数。
void OutOfMem()
{
std::cerr << "???";
std::abort();
}
int main()
{
std::set_new_handler(OutOfMem);
int* test = new int[9999999999999999L];
}
- nothrow new
直到1993年,C++都还要求
operator new
必须在无法分配足够内存时返回null。新一代operator new
应该抛出bad_alloc
异常。但是很多C++程序是在编译器开始支持新规范之前写的。所以委员会不抛弃旧的程序,提供了另一种形式的new,称为”nothrow”形式。
int* ptr = new (std::nothrow) int;
if (ptr == nullptr)
{
//...
}
总结:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
了解new和delete的合理替换时机
有许多理由需要写个自定义的
new
和delete
。包括改善性能、对heap运用错误进行调试、收集heap使用信息。
- 用来检测运用上的错误。
- 为了收集动态分配内存的使用统计数据。
- 为了增加分配和归还的速度。
- 为了降低缺省内存管理器带来的空间额外开销。(泛用性内存分配器往往使用更多内存,针对小型对象而开发的分配器,例如Pool分配器,能消除这些额外的开销)
- 为了弥补缺省分配器中的非最佳齐位。(例如x86架构double的访问最快速的是8byte齐位,但是编译器自带的new不保证8byte齐位)
- 为了将相关对象成簇几种。(成簇集中在尽可能少的内存页上)(placement版本的new和delete有可能完成这样的成簇行为)
- 为了获得非传统的行为。
编写new和delete时需固守常规
operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需要,就应该调用new-handler。它应该有能力处理0-byte申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”。
operator delete 应该收到null指针时不做任何事。class专属版本则还应该处理“比正确大小更大的(错误)申请”。
- 为什么会出现“比正确大小更大的(错误)申请”
GPT:如果在基类中定义了自定义的operator new,但派生类没有覆盖它,那么派生类对象的分配也会调用基类的operator new,从而可能会分配比基类需要更大的内存。以及还有对齐和内存管理的考虑。
写了 placement new 也要写 placement delete
当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有,你的程序可能会发生隐微而时断时续的内存泄漏。
当你声明placement new和placement delete,请确定不要无意识地遮掩了它们的正常版本。
(END)