【C++】《Effective C++》第八章
第八章 定制new和delete
对于程序开发来说,了解C++
内存管理例程的行为是非常重要的。其中两个主角是分配例程和归还例程(operator new
和operator delete
),配角是new-handker
,这是当operator new
无法满足客户的内存需求时所调用的函数。
注意:STL
容器所使用的heap
内存是由容器所拥有的分配器对象(allocator objects
)管理,不是被new
和delete
直接管理。
条款49:了解new-hander
的行为
请记住
set_new_handler
允许客户指定一个函数,在内存分配无法获得满足时被调用。Nothrow new
是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
条款50:了解new
和delete
的合理替换机制
一般处于下列原因可能想要替换编译器提供的operator new
和operator delete
:
- 为了检测运用错误。
- 为了收集动态分配内存的使用统计信息。
- 为了增加分配和归还的速度。
- 为了降低缺省内存管理器带来的空间额外开销。
- 为了弥补缺省分配器中的非最佳齐位。
- 为了将相关对象成簇集中。
- 为了获得非传统的行为。
下面是一个”为了检测运用错误“而实现的简单的 operator new
的例子,通过在首部和尾部插入一个签名,返回中间内存块给程序使用,如果程序在使用内存时发生过在区块前或区块后写入的行为,那么签名就会被修改,因此可以检测这种行为:
static const int signature = 0xDEADBEEF; // 边界符
typedef unsigned char Byte;
void* operator new(std::size_t size) throw(std::bad_alloc) {
// 多申请一些内存来存放占位符
size_t realSize = size + 2 * sizeof(int);
// 申请内存
void *pMem = malloc(realSize);
if (!pMem) throw bad_alloc();
// 写入边界符
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int)))= *(static_cast<int*>(pMem)) = signature;
// 返回真正的内存区域
return static_cast<Byte*>(pMem) + sizeof(int);
}
这个例子主要是展示,它存在很多错误:
- 所有的
operator new
都应该内含一个循环,反复调用某个new-handling
函数,这里却没有。 C++
要求所有operator new
返回的指针都有适当的对齐。这里malloc
返回的指针是满足要求的,但是因为上述实现并不是直接返回malloc
的结果,而是返回一个int
偏移后的地址,因此无法保证它的安全。
请记住
- 有许多理由需要自定的
new
和delete
,包括改善效能、对heap
运用错误进行调试、手机heap
使用信息。
条款51:编写new
和delete
时需固守常规
请记住
operator new
应该包含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存要求,就该调用new-handler
。它也应该有能力处理0bytes
申请。Class
专属版本则还应该处理"比正确大小更大的(错误)申请"。operator delete
应该在收到null
指针时不做任何事。Class
专属版本则还应该处理"比正确大小更大的(错误)申请"。
条款52:写了placement new
也要写placement delete
请记住
- 当你写一个
placement operator new
,请确定也写出类对应的placement operator delete
。如果没有这样做,你的程序可能会发生隐蔽而时断时续的内存泄漏。 - 当你声明
placement new
和placement delete
,请确定不要无意识(非故意)地遮掩了它们的正常版本。