一. 内容
-
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,请确定不要无意识(非故意)地遮掩了它们的正常版本。