条款52:写了placement new也要写placement delete

我只是一个虾纸丫 提交于 2019-12-07 07:58:51

条款52:写了placement new也要写placement delete
    Write placement delete if you write placement new.

    我们都知道当你在写一个new表达式像这样:
    Widget* new_widget = new Widget;
    共有两个函数被调用:一个是用以分配内存的operator new,一个是Widget的default构造函数.
    那么假设我们现在遇到的情况是:第一个函数调用成功,第二个函数却抛出异常.按照常理,在步骤一中所分配的
内存必须取消,否则就会造成内存泄露.而在这个时候客户已经没有能力归还内存了,因为手上没有指向这块内存的
指针,故此任务就落到了C++运行期系统身上.
    为了完成任务,运行期系统当然就会调用步骤一所用的operator new的相应operator delete版本.如果当前要处
理的是拥有正常签名的new和delete版本,这好办!因为正常的operator new签名式:
    void* operator new(std::size_t) throw (std::bad_alloc);
    对应的正常的operator delete签名式:
    void operator delete(void* raw_memory)throw();//global 作用域中的正常签名式
    void operator delete(void* raw_memory,std::size_t size)throw();//class作用域中典型的签名式
    因此,当你只使用正常形式的new和delete,运行期系统毫无问题可以找出那个'知道如何取消new所作所为并恢
复旧观'的delete.然而当你开始声明非正常形式的operator new,即就是附加参数的operator new,问题就出来了.
    为了说明这个问题,我们依然用Widget例子,假设你写了一个class专属的operator new,要求接受一个ostream,
用来logged相关分配信息,同时又写了一个正常形式的class专属operator delete:
    struct Widget{
        //非正常形式的new
        static void* operator new(std::size_t size,std::ostream& log_stream)throw(std::bad_alloc);
        //正常class专属delete.
        static void  operator delete(void* memory, std::size_t size)throw();
        ...
    };
    在这里我们定义:如果operator new接受的参数除了一定会有的那个size_t之外还有其它,这个便是所谓的
placement new.而众多的placement new版本中特别提到的是'接受一个指针指向对象该被构造之处',那样的
operator new长相如下:
    void* operator new( std::size_t, void* memory ) throw();//placement new
    该版本的new已经被纳入C++标准程序库,你只要#include <new>就可以取用它,它的用处就是负责在vector的未
使用空间上创建对象.
    现在让我们回到Widget的声明式,这个Widget将引起微妙的内存泄漏.考虑下面的测试代码,它将在动态创建一个
Widget时将相关的分配信息志记与cerr:
    Widget* new_widget = new( std::cerr ) Widget;
    在说一下我们先前提到的问题,如果内存分配成功,而构造抛出异常,运行期就有责任取消operator new的分配并
恢复旧观.然而运行期系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,所以
上述做法行不通.取而代之的是,运行期系统寻找'参数个数和类型都与operator new相同'的某个operator
delete.如果找打,那就是它该调用的对象.既然这里的operator new接受的类型为ostream&的额外实参,所以对应的
operator delete就应该是:
    void operator delete(void*,std::ostream&)throw();
    类似于new的placement版本,operator delete如果接受额外参数,便称为placement deletes.现在,既然Widget
没有声明placement版本的operator delete,所以运行期系统不知道如何取消并恢复原先对placement new的调用.于
是什么也不做.本例之中如果构造抛出异常,不会有任何operator delete被调用.
    为了消除Widget中的内存泄漏,我们来声明一个palcement delete,对应与那个有志记功能的placement new:
    struct Widget{
        static void* operator new(std::size_t size, std::ostream& log_stream)throw(std::bad_alloc);
        static void  operator delete(void* memory) throw();
        static void  operator delete(void* memory,std::ostream& log_stream)throw();
        ...
    };
    这样改变之后,如果以下语句引发Widget构造函数抛出异常:
    Widget* new_widget = new (std::cerr) Widget; //一如既往,但这次就不在发生泄漏.
    然而如果没有抛出异常(大部分是这样的),客户代码中有个对应的delete,会发生什么事情:
    delete pw; //call normal operator delete
    调用的是正常形式的operator delete,而非其placement版本.请记住:placement delete只有在'伴随placement
new调用而触发的构造函数'出现异常时才会被调用.对着一个指针施行delete绝不会导致调用placement delete.
    还有一点你需要注意的是:由于成员函数的名称会遮盖其外围作用域中的相同名称,你必须小心避免让class专属
news遮盖客户期望的其它news(包括正常版本).默认情况下,C++在global作用域内提供以下形式的operator new:
    void* operator new(std::size_t)throw(std::bad_alloc);//normal new.
    void* operator new(std::size_t,void*)throw();//placement new
    void* operator new(std::size_t, const std::nothrow_t&)throw();//nothrow new.see Item 49.
    如果你在class内声明任何operator news,它会遮掩上述这些标准形式.除非你的意思就是要阻止class的客户使
用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用.对于每一个可用的operator new也
请确定提供对应的operator delete.如果希望这些函数都有着平常的行为,只要令你的class专属版本调用global版
本即可.
    为了完成以上所言的一个简单做法就是建立一个base class,内含所有正常形式的new和delete:
    struct StandardNewDeleteForms{
        //normal new/delete
        static void* operator new(std::size_t size)throw(std::bad_alloc)
        { return ::operator new( size ); }
        static void operator delete(void* memory) throw()
        { ::operator delete( memory ); }
        //placement new/delete
        static void* operator new(std::size_t size,void* pointer)throw()
        { return ::operator new( size, pointer );}
        static void operator delete(void* memory,void* pointer)throw()
        { return ::operator delete( memory, pointer ); }
        //nothrow new/delete
        static void* operator new(std::size_t size,const std::nothrow_t& no_throw)throw()
        { return ::operator new( size, no_throw ); }
        static void operator delete(void* memory,const std::nothrow_t&)throw()
        { ::operator delete( memory ); }
    };
    凡是想自定形式扩充标准形式的客户,可利用继承机制及using声明式(Item 33)取得标准形式:
    struct Widget:public StandardNewDeleteForms{
        using StandardNewDeleteForms::operator new;
        using StandardNewDeleteForms::operator delete;
        static void* operator new(std::size_t size, std::ostream& log_stream) throw(std::bad_alloc);
        static void  operator delete(void* memory,std::ostream& log_stream) throw();
        ...
    };
    好了,本款讨论结束.
    请记住:
    ■ 当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这样做,
你的程序可能会发生隐微而时断时续的内存泄漏.
    ■ 当你声明placement new和placement delete,请确定不要无意识地遮掩它们的正常版本.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!