在编写自己的operator new和operator delete时需要遵守哪些规则?
让我们从operator new开始。
实现一致性operatoe new必得返回正确的值,内存不足时必得调用new-handling函数,必须有对付零内存需求的准备,还需避免不慎掩盖正常形式的new。
operator new的返回值十分单纯。若它有能力供应客户申请的内存,就返回一个指针指向那块内存。若没有那个能力,就遵循文章49描述的规则,并抛出一个bad_alloc异常。
然而也不是非常单纯,因为operator new实际上不只一次尝试分配内存,并在每次失败后调用new-handling函数。这里假设new-handling函数也许能够做某些动作将某些内存释放出来。只有当指向new-handling函数的指针是null,operator new才会抛出异常。
奇怪的是C++规定,即使客户要求0 byte,operator new也得返回一个合法指针。这种看似诡异的行为其实是为了简化语言的其他部分。
下面是个operator new伪码:
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
if (size == 0)
size = 1;
while (true)
{
尝试分配size byte;
if (分配成功)
return (一个指针,指向分配得来的内存);
//分配失败,找出目前的new-handling函数
new_handler globalHanler = set_new_handler(0);
set_new_handler(globalHanler);
if (globalHanler)(*globalHanler)();
else
throw std::bad_alloc();
}
}
这里的伎俩是把0 byte申请量为1 byte申请量。
许多人没有意识到operator new成员函数会被derived class继承。这会导致某些有趣的复杂度。
注意上述的operator new伪码中,函数尝试分配size byte(除非size是0)。那非常合理,因为size是函数接受的实参。然而,写出定制型内存管理器的一个最常见理由是为针对某特定class的对象分配行为提供最优化,却不是为了该class的任何derived class。也就是说,针对class X而设计的operator new,其行为很典型地只为大小刚好为sizeof(x)的对象而设计。然而一旦被继承下去,有可能base class的operaotr new被调用用以分配derived class对象:
class Base {
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
//...
};
//假设Derived未声明operator new
class Derived : public Base {/*...*/ };
Derived* p = new Derived;//这里调用的是Base::operator new
若Base class专属的operator new并非被设计用来对付上述情况,处理此情势的最佳做法是将“内存申请量错误”的调用行为改采标准operator new,像这样:
void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
if (size != sizeof(Base))//若大小错误
return ::operator new(size);//令标准的operator new进行处理
//...//否则在这里处理
}
你不能在Base::operator new[ ]假设array的每个元素对象的大小是sizeof(Base),这也就意味你不能假设array的元素对象个数是(byte申请数)/sizeof(Base)。此外,传递给operator new[ ]的size_t参数,其值有可能比“将被填以对象”的内存数量更多。
这就是撰写operator new时你需要奉行的规则。
operator delete情况更简单,你需要记住的唯一事情就是C++保证“删除null指针永远安全”,所以你必须兑现这项保证。
下面是non-member operator delete的伪码:
void operator delete(void* rawMemory) throw()
{
//若当被删除的是个null指针,那就什么都不做
if (rawMemory == 0)
return;
//现在,归还rawMemory所指的内存
//...
return;
}
这个函数的member版本也很简单,只需要多加一个动作检查删除数量。万一你的class专属的operator new将大小有误的分配行为转交给::operator new执行,你也必须将大小有误的删除行为i转交::operator delete执行:
class Base {//一如以往,但此刻重点在operator delete
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 delete(void* rawMemory, std::size_t size) throw()
{
if (rawMemory == 0) return;//检查null指针
//若大小错误,令标准版operator delete处理此一申请
if (size != sizeof(Base))
{
::operator delete(rawMemory);
return;
}
//现在,归还rawMemory所指内存
//...
return;
}
有趣的是,若即将被删除的对象派生自某个base class而后者欠缺virtual析构函数,那么C++传给operator delete的size_t数值可能不正确。若你的base class遗漏virtual析构函数,operator delete可能无法正确运作。
总结
1.operator new应该内含一个无穷循环,并在其中尝试分配内存,若它无法满足内存需求,就该调用new-handler。它也应该有能力处理0 byte申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”。
2.operator delete应该在受到null指针时不做任何事。class专属版本则还应该处理“比正确大小更大的(错误)申请”。