EffectiveC++-条款52:写了 placement new 也要写 placement delete

一. 内容
placement new 和 placement delete 在 C++ 中并不常见,如果不熟悉它们,不要感到害怕和忧虑。

回忆条款16和17,当你写一个 new 表达式时:

FString *String = new FString("HelloWorld");
共有两个函数被调用:一个是用以分配内存的 operator new,一个是 FString 的构造函数。

假如第一个函数调用成功,第二个函数却抛出异常。那么运行期系统必须回收第一个函数分配的内存,否则就会发生资源泄漏。但前提时,系统必须知道哪一个 operator delete 该被调用,因为可能存在多个 operator delete 函数。这就是本条款的要探讨的问题所在。

所谓 placement new/delete ,就是特定位置上的 new/delete,它们接受额外的参数 。当人们谈及 placement new 时,大多数说到的是具有唯一额外实参 void* 的 operator new,少数时候才是指具有任意额外实参的 operator new。

当抛出异常时,运行期系统会寻找参数个数和类型都与 operator new 相同的某个 operator delete。比如 operator new 额外接受一个 string 参数,那么 operator delete 也需要提供一个额外的 string 参数。如果并没有这样的 operator delete 函数,那么系统什么也不会做,内存就会泄漏掉。

但值得注意的时,我们可以显式调用 placement new,但是 placement delete 只有在 placement new 的调用构造函数异常时才会被系统调用。即使你对一个用 placement new 申请出的指针使用 delete,也绝不会调用 placement delete。这意味着额外的参数并不提供实际的作用。

这意味着如果要处理 placement new 相关的内存泄漏问题,我们必须同时提供一个正常版本的 delete 和 placement 版本的 delete。前者用于构造期间无异常抛出,后者用于构造期间有异常抛出。

顺带一提的是,注意同名函数遮掩调用的问题,当你为 class 声明了 placement new 时,客户是无法使用标准版的 operator new 的,因为 derived class 声明的 operator new 会遮掩标准版本和 base class 版本。

所以如果你需要的客户在使用标准版本不受影响,也需要同时提供标准版的定义。

满足以上注意事项的一个简单做法是,建立一个 base class,内含所有标准版本的 new/delete,凡是想以写 placement 版本的 class 都可以继承自它,并使用 using 声明式使得标准版本在类中可见:
 

class FNewDeleteSupport {
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* RawMemory) throw() {
        ::operator delete(RawMemory);
    }
    //placement new/delete
    static void* operator new (std::size_t Size,void *Ptr) throw() {
        return ::operator new(Size,Ptr);
    }
    static void operator delete (void* RawMemory,void *Ptr) throw() {
        ::operator delete(RawMemory,Ptr);
    }
    //nothrow new/delete
    static void* operator new (std::size_t Size,const std::nothrow_t& Nothrow) throw() {
        return ::operator new(Size,Nothrow);
    }
    static void operator delete (void* RawMemory,const std::nothrow_t& Nothrow) throw() {
        ::operator delete(RawMemory);
    }
};
class FDemo:public FNewDeleteSupport {
public:
    using FNewDeleteSupport::operator new;
    using FNewDeleteSupport::operator delete;

    //custom new/delete
    static void* operator new (std::size_t Size,std::string User) throw(std::bad_alloc) {
        std::cout<<User<<"使用了内存";
        return ::operator new(Size);
    }
    static void operator delete (void* RawMemory,std::string User) throw() {
        ::operator delete(RawMemory);
    }
};

二. 总结
当你写一个 placement operator new,请确定也写出了对应的 placement operator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏。
当你声明 placement new 和 placement delete,请确定不要无意识(非故意)地遮掩了它们的正常版本。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值