51:编写new和delete时需固守常规

文章详细阐述了在C++中自定义operatornew和operatordelete时应遵循的规则,包括处理内存分配失败、0字节请求、new-handling函数的使用,以及处理基类和派生类关系时的注意事项。operatornew需要无限循环尝试分配内存,处理异常,并确保能处理0字节请求。operatordelete则需处理空指针,并在必要时调用标准版本处理错误大小的删除操作。
摘要由CSDN通过智能技术生成

在编写自己的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专属版本则还应该处理“比正确大小更大的(错误)申请”。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值