• effective C++ 条款 52:写了placement new也要写placement delete


    Widget* pw = new Widget;

    共有两个函数被调用:一个分配内存的operator new,一个Widget的default构造函数。

    假设第一个调用成功,第二个却抛出异常。步骤一所分配内存必须取消并恢复旧观,否则会造成内存泄漏。这时,客户没能力归还内存,因为Widget构造函数抛出异常,pw尚未被赋值,客户手上也就没有指针指向该被归还的内存。取消步骤一,并恢复旧观的责任就落到c++运行系统身上。

    运行期系统会高兴的调用步骤一所调用operator new的相应的operator delete版本,前提是他必须知道哪一个operator delete被调用(可能有许多个)。如果目前面对的是拥有正常签名式的new和delete,并不是问题,正常的new:

    void* operator new(std::size_t) throw(std::bad_alloc);

    对应正常的delete:

    void operator delete(void* rawMemory) throw();//global 作用域中的正常签名式

    void operator delete(void* rawMemory, std::size_t size) throw();//class作用域中的典型签名式。

    但是当你开始声明非正常形式的operator new,也就是带附加参数的operator new,“究竟哪一个delete伴随这个new”的问题就浮现了:

    class专属的operator new要求接受一个ostream,志记相关分配信息,同时又写了个正常形式的class专属operator delete:

    class Widget{
    public:
        static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
        static void* operator delete(void* pMemory, std::size_t size) throw();
    };

    如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这个便是所谓的placement new,众多placement new中特别有一个是“接受一个指针指向对象该被构造之处”,那样的operator new长相如下:

    void* operator new(std::size_t, void* pMemory) throw();

    这个版本已经被纳入c++标准库,只要#include <new>就可以取用它。这个new的用途之一是负责在vector的未使用空间上创建对象。他同时是最早的placement new版本,这个函数的命名根据:一个特定位置上的new

    Widget* pw = new(std::cerr) Widget;

    这个如果Widget构造函数抛出异常,运行期系统无法知道真正被调用的那个new如何运作,因此无法取消分配并恢复旧观。运行期系统寻找“参数个数和类型都与operator new相同的”某个operator delete。应该是:

    void operator delete(void*, std::ostream&) throw();

    被称为placement deletes。现在,既然Widget没有声明placement版本的operator delete,所以运行期系统什么也不做。如果Widget构造函数抛出异常,不会有任何operator delete被调用。

    规则很简单:如果一个带额外参数的operator new没有“带相同额外参数”的对应版operator delete,那么当new的内存分配动作需要取消并恢复时就没有任何operator delete会被调用。所以有必要声明一个placement delete,对应于那个有志记功能的placement new:

    class Widget{
    public:
        static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
        static void* operator delete(void* pMemory, std::size_t size) throw();
        static void* operator delete(void* pMemory, std::ostream& logStream) throw();
    };

    这样改变后,如果Widget构造函数抛出异常:

    Widget* pw = new (std::cerr) Widget;

    对应的placement delete会被自动调用,让Widget有机会确保不泄露内存。

    这个语句:

    delete pw;//调用正常的operator delete

    placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。

    这意味对所有placement new我们必须同时提供一个正常的delete和一个placement版本。

    由于成员函数的名称会掩盖其外围作用域中的相同名称,你必须小心让class专属的news掩盖客户期望的其他news(包括正常版本)。

    如果你有一个base class,其中声明唯一一个placement operator new,客户会发现他们无法使用正常形式的new:

    class Base{
    public:
        static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);
    };

    Base* pb = new Base;//错误,正常形式的operator new被掩盖

    Base* pb = new (std::cerr) Base;//正确,调用palcement new

    同样,derived classes中的operator new会掩盖global版本和继承而得的operator new版本:

    class Derived : public Base{
    public:
        static void* operator new(std::size_t size)throw(std::bad_alloc); //重新声明正常形式的new
    };

    Derived* pd = new (std::cerr) Derived;        //错误,Base的placement new被掩盖了
    Derived* pd = new Derived;                        //正确,

    缺省情况下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

    如果你在class内声明任何operator news,它会遮掩上述这些标准形式。除非你的意思就是要阻止class的客户使用这些形式,否则确保他们在你生成的任何定制型operator new之外还可用。

    一个简单的做法是,建立一个base class,内含所有正常形式的new和delete:

    class StandardNewDeleteForms{
    public:
        //normal new/delete
        static void* operator new(std::size_t size)throw(std::bad_alloc)
        {return ::operator new(size);}
        static void operator delete(void* pMemory) throw()
        {::operator delete(pMemory);}
        //placement new/delete
        static void*  operator new(std::size_t size, void* ptr) throw()
        {return ::operator new(size, ptr);}
        static void operator delete(void* pMemory, void* ptr)throw()
        {return ::operator delete(pMemory, ptr);}
        //nothrow new/delete
        static void* operator new(std::size_t size, const std::nothrow_t& nt)throw()
        {return ::operator new(size, nt);}
        static void operator delete(void* pMemory, const std::nothrow_t&) throw()
        {::operator delete(pMemory);}
    };

    凡是想自定义形式扩充标准形式的客户,可利用继承机制及using声明式取得标准形式:

    class Widget: public StandardNewDeleteForms{
    public:
        using StandardNewDeleteForms::operator new;
        using StandardNewDeleteForms::operator delete;
        static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);
        static void operator delete(void* pMemory, std::ostream& logStream) throw();
    };

  • 相关阅读:
    TensorFlow 用神经网络解决非线性问题
    找第一个只出现一次的字符
    scanf("%[^ ]",str)
    求第n个质数
    得到任意数字的各个数位
    Tensorflow 对上一节神经网络模型的优化
    Tensorflow 安装 和 初识
    算法竞赛入门 (一)语言篇 数组和字符串 2
    char* a = "abc" 和 char a[] = "abc" 之间的区别
    Python实现英文文章加密传送,收到后进行解密
  • 原文地址:https://www.cnblogs.com/lidan/p/2358706.html
Copyright © 2020-2023  润新知