编写new和delete时需固守常规——条款51

        条款50已解释什么时候你会想要写个自己的operator new和operator delete,但并没有解释当你那么做时必须遵守什么规则。这些规则不难奉行,但其中一些并不直观,所以知道它们究竟是些什么很重要。

        让我们从operator new开始。实现一致性operator new必须得返回正确的值,内存不足时必得调用new-handling函数(见条款 49),必须有对付零内存需求的准备,还需避免不慎掩盖正常形式的new——虽然比较偏近class的接口要求而非实现要求。正常形式的new描述于条款52。

        operator new的返回值十分单纯。如果它有能力供应客户申请的内存,就返回一个指针指向那块内存。如果没有那个能力,就遵循条款49描述的规则,并抛出一个bad_alloc异常。

        然而其实也不是非常单纯,因为operator new实际上不只一次尝试分配内存,并在每次失败后调用new-handling函数。这里假设new-handling函数也许能够做某些动作将某些内存释放出来。只有当指向new-handling函数的指针是null,operator new才会抛出异常。

        奇怪的是C++规定,即使客户要求0bytes,operator new也得返回一个合法指针。这种看似诡异的行为其实是为了简化语音其他部分。下面是个non-member operator new伪代码:

void* operator 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();
        }
    }
}

        这里的伎俩是把0 bytes申请量视为1 bytes申请量。看起来有点令人厌恶,但做法简单、合法、可行,而且比较客户多久才会发出一个0 bytes申请呢?

         条款49谈到operator new内含一个无穷循环,而上述伪代码明白表明这个循环;“while(true)”就是那个无穷循环。退出循环的唯一办法是:内存被成功分配或new-handling函数做了一件描述于条款49的事情:让更多内存可用、安装另一个new-handler、卸除new-handler、抛出bad_alloc异常(或派生物),或是承认失败而直接return。现在,对于new-handler为什么必须做出其中某些事你应该很清楚了。如果不那么做,operator new内的while循环永远不会结束。

        许多人没有意识到operator new成员函数会被derived classes继承。这会导致某些有趣的复杂度。注意上述operator new伪代码中,函数尝试分配size bytes(除非size是0)。那非常合理,因为size是函数接受的实参。然而就像条款50所言,写出定制型内存管理器的一个最常见理由是为针对某特定class的对象分配行为提供最优化,却不是为了该class的任何derived classes。也就是说,针对class X而设计的operator new,其行为很典型地只为大小刚好为sizeof(X)的对象而设计。然而一旦被继承下去,有可能base class的operator new被调用用以分配derived class对象:

class Base {
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
};
class Derived: public Base  // 假设Derived未声明operator new
{ ... };
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起而处理。
    ...                                   // 否则在这里处理。
}

        “等一下!”我听到你大叫,“你忘了检验size等于0这种病态但是可能出现的情况!”。C++裁定所有非附属(独立式)对象必须有非零大小(见条款39)。因此sizeof(Base)无论如何不能为零。

        如果你打算控制class专属之“arrays内存分配行为”,那么你需要实现operator new的array兄弟版:operator new[]。这个函数通常被称为“array new”,因为很难想出如何发音“operator new[]”。如果你决定写个operator new[],记住,唯一需要做的一件事就是分配一块为加工内存,因为你无法对array之内迄今尚未存在的元素对象做任何事情。实际上你甚至无法计算这个array将含有可能经由继承被调用,将内存分配给“元素为derived class对象”的array使用,而你当然知道,derived class对象通常比其base class对象大。

        因此,你不能在Base::operator new[]内假设array的每个元素对象的大小是sizeof(Base),这也就意味你不能假设array的元素对象个数是(bytes申请数)/sizeof(Base),此外,传递给operator new[]的size_t参数,其值有可能比“将被填以对象”的内存数量更多,因为条款16说过,动态分配的arrays可能包含额外空间来存放元素个数。

        这就是撰写operator new时你需要奉行的规矩。operator delete情况更简单,你需要记住的唯一事情就是C++保证“删除null指针永远安全”,所以你必须兑现这项保证。下面是non-member operator delete的伪代码

void operator delete(void * rawMemory) throw()
{
    if (rawMemory == 0) return;  // 如果将被删除的是个null指针,那就什么都不做
    // 现在,归还rawMemory所指的内存;
}

        这个函数的member版本也很简单,只需要多加一个动作检查删除数量。万一你的class专属的operator new将大小有误的分解行为转交::operator new指向,你也必须将大小有误的删除行为转交 ::operator delete执行:

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 delete(void* rawMemory, std::size_t size) throw()
{
    if (rawMemeory == 0) return;
    if (size != sizeof(Base)) {
        ::operator delete(rawMemory);
        return;
    }
    // 现在,归还rawMemory所指的内存;
    return;
}

        有趣的是,如果即将被删除的对象派生自某个base class而后者欠缺virtual析构函数,那么C++传给operator delete的size_t数值可能不正确。这是“让你的base classes拥有virtual析构函数”的一个够好的理由;条款7还提过一个更好的理由。我就不岔开话题了,此刻只要你提高警觉,如果你的base classes遗漏virtual析构函数,operator delete可能无法正确运作。

请记住

  • operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0 bytes申请。Class operator delete应该收到null指针时不做任何事。Class专属版本则还应该处理“比正确大小更大的(错误)申请”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值