第八章:定制new和delete
条款49:了解new-handler的行为
- set_new_handler的参数是一个指针,指向operator new无法分配足够内存时该被调用的函数。返回值也是个指针,指向set_new_handler被调用前正在执行的那个new-handler函数。
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new handler p) throw();
}
例子
void outOfMem()
{
std::cerr << "unable to satisfy request for memory \n";
abort();
}
int main()
{
std::set_new_handler(outOfMem);
int* pBig = new int[100000000L];
}
条款50:了解new和delete的合理替换时机
- 用来检测运用上的错误:例如在new所得内存上多次delete则会导致不确定行为。写入点在分配区块尾端之后(overruns)或写入点在分配区块起点之前(underruns)。我们可以写入签名(signature),用于查看分配的指针。
- 为了强化效能:处理内存碎片问题。定制版的operator new 和operator delete性能胜过缺省版本,体现在所需内存上,已经时间上。
例子:一个定制型的operation new
static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
siez_t realSize = size + 2*sizeof(int); //增加大小,使之能够写入两个signature
void* pMem = malloc(realSize); //调用malloc取得内存
if(!pMem) throw bad_alloc();
//将signature写入内存的最前段落和最后段落
*(static_cast<int*>(pMem)) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) = signature;
//返回指针,指向位于第一个signature之后的内存位置
return static_cast<Byte*> (pMem) + sizeof(int);
}
- 齐位(alignment):许多计算机体系结构要求特定的类型必须放在特定的内存地址上。例如它可能要求指针的地址必须是4倍数或double的地址必须是8倍数。这样子会提高效率。
- C++要求所有operator new返回指针都有适当的对齐。malloc就是在这样子的要求下工作。但自定义的new却没有。
- Boost中的pool是一个搞笑的内存分配器。
条款51:编写new和delete时需固守常规
operator new 伪代码
void* oprator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
if(size == 0){
size = 1;
}
while(true){
尝试分配size bytes;
if(分配成功)
return (一个指针,指向分配得来的内存);
//分配失败;找出目的的new-handling函数
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if(globalHandler) *(globalHandler)();
else throw std::bad_alloc();
}
}
-
operator new实际上不只一次尝试分配内存,并在每次失败后调用new-handling函数。只有当指向new-handling函数的指针是null,operator new才会抛出异常。
-
operator new应该包含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就应该调用new-handler.它也应该有能力处理0bytes申请。
class Base{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void* rawMemory, std::size_t size) throw();
...
}
void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
if(size != sizeof(Base)) //如果大小错误,令标准operator new处理
return ::operator new(size);
... //否则在这里处理
}
- operator delete应该在收到null指针时不做任何事情。
void Base::operator delete(void* rawMemory, std::size_t size) throw()
{
if(rawMemory == 0) return; //null指针,什么都不做
if(size!=sizeof(Base)){
::operator delete(rawMemory); //如果大小错误,令标准版operator delete处理
return ;
}
... //归还rawMemory内存
return;
}
- 小结:遇到无法处理的情况,交给原生的new 和 delete处理。
条款52:写了placement new 也要写placement delete
Widget* pw = new Widget;
共有两个函数被调用,一个是用以分配内存的operator new,一个是widget的default构造函数。
如果第一个函数调用成功,但是第二个函数调用失败。那么必须回收分配出去的内存,否则会造成内存泄漏。这一工作由C++运行期系统身上。
运行期系统会调用步骤一所调用的operator new函数对应的operator delete版本。如果有多个版本的new,那也必须对应多个版本的delete,否则会产生异常。
class StandardNewDeleteForms
{
public:
//normal new/delete
static void* operator new(std::size_t size) throw(std::bad_alloc)
{ return ::operator new(size); }
static void operator delete(void* pMemory) throw()
{ return delete(pMemory); }
//placement new/delete
static void* operator new(std::size_t size, void* ptr) throw()
{ return ::operator new(size, ptr); }
static void operator delete(void* pMemory, void* ptr) throw()
{ return ::operator delete(pMemory, ptr); }
}