《Effective C++》:条款52:写了placement new也要写placement delete

来源:互联网 发布:单片机协议栈开发 编辑:程序博客网 时间:2024/05/19 08:01

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

placement new和placement delete是C++经常用到但是却不常见的两个操作符。当我们使用new创建一个对象时

Widget* pw=new Widget;

有两个函数被调用,第一个函数就是operator new,用以分配内存,第二个是Widget的default构造函数。如果第一个函数调用成功,但是第二个函数调用失败,这时需要释放第一步开辟的内存,否则就造成了内存泄露。这个时候,客户没有能力去归还内存,因为如果Widget构造函数抛出异常,那么pw尚未被赋值,客户手中的指针还没有指向开辟的内存。释放内存的任务落到了C++运行期系统身上。

运行期系统会调用第一个函数operator new所对应的operator delete版本。因为operator delete可能有多个版本。正常的operator new和对应的operator delete

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

class作用域内有典型签名的operator delete
void operator delete(void* rawMemory, std::size_t size) throw();
如果使用正常的operator new和operator delete,运行期系统可以找到如何释放new开辟内存的delete函数。但是如果使用非正常形式的operator new,究竟使用那个delete就会有问题了。

那个例子来说明。假设编写一个class专属的operator new,要接接收一个ostream,用来志记相关分配信息,同时又写了一个正常形式的class专属operator delete:

    class Widget{    public:        ……        static void* operator new(std::size_t size, std::ostream& logStream)//非正常形式的new            throw(std::bad_alloc);        static void operator delete(void* pMemory, std::size_t size)//正常的class专属delete            throw();        ……    };

这个设计有问题,在讨论问题签,先说明若干术语。operator new接受的参数除了必有的size_t之外还有其他,这便是placement new。所以上述的operator new是个placement版本。众多placement new版本中,有一个特别有用的是“接受一个指针指向对象该被构造之处”,这个operator new形式如下

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

这个版本的new已被纳入C++标准程序库,在头文件#include。这个placement new是最早的版本,根据命名:一个特定位置上的new。当人们谈到placement new时,大多时候谈的是这一特定版本,即还有额外实参void*。有一点比较普遍,术语placement new意味着带有额外参数的new,因为另一个术语placement delete直接派生自它。

现在再来看一下Widget class的声明式,这个class会引起内存泄露。例如动态创建一个Widget时将相关信息分配信息志记于cerr:

Widget* pw=new (std:cerr) Widget;//调用operator new,并传递cerr作为ostream实参

如果内存分配成功,但是Widget构造函数抛出异常,运行期系统要释放operator new开辟的内存。但是运行期系统不知道真正被调用的operator new如何运作。运行期系统寻找参数个数和类型都与operator new相同的operator delete。这里对应的operator delete为:

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

和operator new的placement版本类似,接收额外参数的operator delete成为placement delete。上面Widget没有placement版本的operator delete,所以运行期系统不知道如何释放operator new开辟的内存。所以Widget class应该定义如下:

 class Widget{    public:        ……        static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);        static void operator delete(void* pMemory) throw();        static void operator delete(void* pMemory, std::ostream& logStream) throw();    };    Widget* pw=new (std:cerr) Widget;//调用operator new,并传递cerr作为ostream实参

这样如果Widget构造函数抛出异常,就会调用对应版本的placement delete。但是如果没有异常,这样调用

delete pw;

就会调用正常版本的operator delete。placement delete只有在placement new调用构造函数抛出异常时才会被调用。

需要注意的是,因为成员函数的名称会掩盖其外围作用域中相同名称的函数(**条款**33),所以要小心避免class专属的new掩盖客户希望调用的new。例如,你有一个base class,其中声明唯一一个placement

operator new    class Base{    public:        ……        static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);//会掩盖global new        ……    };    Base* pb=new Base;//错误,因为正常形式的operator new被掩盖    Base* pb1=new (std::cerr) Base;//调用Base的placement new

在derived class的operator new会掩盖继承而来的operator new和global版本的new

 class Derived: public Base{    public:        ……        static void* operator new(std::size_t size) throw(std::bad_alloc);//重新声明正常形式的new    };    Derived* pd=new (std::clog) Derived;//错误,因为Base的placement new被掩盖了    Derived* pd1=new Derived;//正确在缺省情况下,C++在global作用域内提供以下形式的operator newvoid* operator(std::size_t) throw(std::bad_alloc);//normal new    void* operator(std::size_t, void*) throw();//placement new    void* operator(std::size_t, const std::nothrow_t&) throw();//nothrow new

在class内声明任何形式的operator new都会掩盖上面这些标准形式。对于每一个可用的operator new,要确保提供了对应形式的operator delete。

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

    class StadardNewDeleteForms{    public:        //normal        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        static void* operator new(std::size_t size, void* ptr) throw(std::bad_alloc)        {return ::operator new(size, ptr);}        static void operator delete(void* pMemory, void* ptr) throw()        {::operator delete(pMemory, ptr);}        //nothrow        static void* operator new(std::size_t size, const std::nothrow_t& nt) throw(std::bad_alloc)        {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 detele(std::size_t size, std::ostream& logStream) throw();    };

总结

  • 当编写一个placement operator new时,也要编写对应版本的placement operator delete。否则就可能造成隐蔽的内存泄露。
  • 当声明了placement new和placement delete时,就会掩盖正常版本。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 新办的手机号已被注册支付宝怎么办 新办的移动卡不能注册支付宝怎么办 新办的宝卡支付宝注册过怎么办 办支付宝后银行卡被盗刷怎么办 微信扫二维码送平衡群发了怎么办车 拼多多没收到货点了确认收货怎么办 拼多多收货地址填错了怎么办 京东订单申请退款已发货怎么办 手机播放声音的地方进水了怎么办 手机出声音的地方进水了怎么办 手机听声音的地方进水了怎么办 装修公司我只要基装她不肯怎么办 雄迈未来家庭忘记账号和密码怎么办 地方文件和国家政策有冲突怎么办? 网上贷款放款成功没有到账怎么办 我来贷放款中不到账怎么办 手持身份证被别人网贷了怎么办 身份证被别人做了网贷怎么办 身份证丢了被别人网贷了怎么办 户口身份证被注销了网贷怎么办 首付交了贷款办不下来怎么办 付首付后贷款没有批下来怎么办 交了首付贷款没有办下来怎么办 买房交了首付贷款没批下来怎么办 交完首付办不下来贷款怎么办 房子交了首付贷款办不下来怎么办 浦发银行办理房贷流水不够怎么办 房贷审批过程中突然换工作怎么办 建行快贷结清后无额度怎么办 建设银行快贷逾期2个月怎么办 全职速卖通一个月没订单要怎么办 买家点的好评写的差评怎么办 手机流量把话费扣没了怎么办 街电忘记还怎么办3天了 街电1个星期没还怎么办 苹果手机设置密码忘记问题了怎么办 买房付了首付后无法办按揭怎么办 苹果x导入通讯录不显示名字怎么办 苹果切换了id通讯录没有了怎么办 换了id通讯录记录没有了怎么办 4s升级系统通讯录没了怎么办