zhouqijie

定制new和delete



了解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];
}

直到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的合理替换时机

有许多理由需要写个自定义的newdelete。包括改善性能、对heap运用错误进行调试、收集heap使用信息。

  1. 用来检测运用上的错误。
  2. 为了收集动态分配内存的使用统计数据。
  3. 为了增加分配和归还的速度。
  4. 为了降低缺省内存管理器带来的空间额外开销。(泛用性内存分配器往往使用更多内存,针对小型对象而开发的分配器,例如Pool分配器,能消除这些额外的开销)
  5. 为了弥补缺省分配器中的非最佳齐位。(例如x86架构double的访问最快速的是8byte齐位,但是编译器自带的new不保证8byte齐位)
  6. 为了将相关对象成簇几种。(成簇集中在尽可能少的内存页上)(placement版本的new和delete有可能完成这样的成簇行为)
  7. 为了获得非传统的行为。



编写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)