《Effective C++》:条款51:编写new和delete时需固守常规

条款 50已经说明为什么要写自己的operator new和operator delete,本条款解释在编写时遵循什么守则。

从operator new开始。operator new必须返回正确的值,内存不足时必须调用new-handling函数,要有对付零内存需求的准备,避免不慎掩盖正常形式的new–这比较偏近class接口的要求而非实现要求。正常形式的new描述与条款 52。

operator new如果申请内存成功,就返回指向那块内存的指针,失败则遵循条款 49描述,抛出bad_alloc异常。它实际上不知一次尝试内存分配,在每次失败后都调用new-handling函数。这里假设new-handling函数能做某些动作将一些内存释放出来。只有指向new-hangling函数的指针为,才会抛出异常。C++规定,即使客户要求0 byte,operator new也要返回一个合法指针。下面是个non-member operator new的伪码(pseudocode):

void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    if(size==0){//处理0-byte申请
        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();
    }
}

如果在多线程环境下,还需要某种锁机制,以便处理new-handling函数背后的global数据结构。

上面包含一个死循环,退出此死循环唯一办法就是内存分配成功(假设new-handling不为null),所以new-handling函数做的事是:让更多内存可用、安装另一个new-handler、卸载new-handler、抛出bad_alloc异常(或其派生类),或承认失败直接return。

上面的operator new成员函数可能会被derived classes继承。注意分配内存大小size,它是函数接收的实参。**条款**50提到,定制内存分配器往往是为了特定的class对象,以此来优化,不是为了该class的derived classes。

class Base{
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc)
    ……
};
class Derived:public Base
{……};//假设为重新定义operator new
Derived* p=new Derived;//这里调用了Base::operator new

如果是class专属的operator new,应该改为这样:

void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
    if(size!=sizeof(Base))
        return ::operator new(size);//使用标准的operator new
    ……
}

如果你打算控制class专属版的arrays内存分配,那么需要实现operator new[]。编写operator new[]时,唯一要做的一件事就是分配一块未加工的内存(raw memory),因为你无法对array之内迄今尚未存在的元素对象做任何事。甚至我们无法知道这个array含有多少个元素对象。可能你不知道每个对象多大,因为base class的operator new[]有可能经由继承被调用,将内存分配给derived class对象的array使用。

所以不能再Base::operator new[]中假设每个元素对象大小是sizeof(Base),这样就是说你不能假设array元素个数是(bytes申请数/sizeof(Base))。此外,传递给operator new[]的size_t参数,其值有可能比将辈填对象的内存大一些,因为条款 16提过,动态分配的arrays可能包含额外空间用来存放元素个数。

operator delete情况就简单很多,但是要记住,C++保证删除指针永远安全。下面是non-member operator delete的伪码(pseudocode):

void operator delete(void* rawMemory) throw()
{
    if(rawMemory==0) return;

    归还rawMemory所指内存;
}

这个函数的member版本也很简单,只需多加一个检查删除数量。

void Base::operator delete(void rawMemory, std::size_t size) throw()
{
    if(rawMemory==0) return;
    if(size!=sizeof(Base)){
        ::operator delete(rawMemory);
        return ;
    }
    归还rawMemory所指内存;
    return ;
}

如果即将删除的对象派生自某个base class而后者没有virtual析构函数,那么C++传给operator delete的size_t数值可能不正确。

总结

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

你可能感兴趣的:(《Effective,C++》)