【Effective C++】读书笔记 条款52:写了placement new 也要写placement delete
2017-05-18 21:13
471 查看
定制new 和 delete
条款52:写了placement new 也要写placement delete
1. new 操作中的内存泄漏
有如下一个new操作A *pb = new A; //A是一个自定义class类型
我们知道,对于一个new操作,一共有如下步骤,
1. 调用operator new请求分配内存。
2. 调用A的构造函数进行构造。
3. 返回指针
假设第一步已经操作成功,但是在B的构造函数被调用的时候却发生了异常,所以为了防止内存泄漏,步骤一中的内存分配必须取消来恢复原状。但是此时pb并未被赋值相应的值,客户代码是没有能力来进行内存释放的。所以这个善后工作就交给了C++运行期系统。C++运行期系统就会调用与operator new相应的operator delete版本来释放内存。这样就保证了不会造成潜在的内存分配。
2. 定义与placement new 相对应的 placement delete 来防止内存泄漏
placement new 和 placement delete
先解释一个术语,placement new。我们知道,对于一个operator new,其第一个参数一定是size_t,但是我们想定义一个新的operator new,它所接受的参数就可能除了size_t之外还有其他类型。这就是placement new的一个含义。其是指的是,接受任意额外实参的operator new。
除此之外,对于C++自身而言,它还提供了一个版本的operaotr new。
//C++98版本 placement void* operator new (std::size_t size, void* ptr) throw() //C++11版本 placement void* operator new (std::size_t size, void* ptr) noexcept;
这个版本的operator new 接受一个 void *类型的额外参数,函数功能只是返回这个指针地址,来指示new操作中第二个步骤的构造函数在此指针所指地址上进行构造对象。这个版本的operator new也被称为 placement new。事实上,这个术语的名称也是来自于此。
所以人们谈到,placement new的时候,大多数是提到的指唯一额外实参是 *的operator new,少数时候是指的接受任意额外实参的operator new。
与此术语相对应的有placement delete。同样有两个含义,一个是C++提供的额外接受一个void *的operator delete。
//C++98 placement (3) void operator delete (void* ptr, void* voidptr2) throw(); //C++11 placement (3) void operator delete (void* ptr, void* voidptr2) noexcept;
这个版本的placement delete 对应于相应的placement new,第二个 void 的参数无实际意义,主要是为了和额外接受void 参数的形成对应。函数功能就是简单的返回相应的函数指针。
第二个含义就是接受任意额外实参的operator delete。
提供相应的placement delete
根据第一部分,我们能够知到,在进行new操作的时候,如果构造函数出现异常,运行期系统有责任取消operator new的内存分配恢复原状。此时运行期系统就会调用相对应的operator delete来进行善后工作。
对于C++自身提供的operator new我们当然不用操心它的善后工作。
但是对于我们自身定义的operator new,如何寻找相对应的delete操作呢?
对于自定义的placement new,运行期系统将会寻找参数个数和类型都与operator new相同的某个 operator delete来进行善后工作。
因此,对于一个带有额外参数的operator new没有带有相同额外参数的对应版本operator delete,那么new的内存分配动作需要取消来恢复原状的时候,就不会有任何的operator delete被调用。
就书上的例子来看。假设编写一个class专属的operator new,要接收一个ostream,用来记录相关分配信息,同时又写了一个正常形式operator delete:
class Widget{ public: …… static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); //非正常形式的new static void* operator new(std::size_t size) throw(std::bad_alloc); //正常形式的new static void operator delete(void* pMemory) throw(); //正常的delete形式 …… };
并且采用如下形式new一个Widget。
Widget* pw=new (std:cerr) Widget;//调用operator new,并传递cerr作为ostream实参
注意,这个语句在Widget构造函数抛出异常的时候会泄漏内存,正如我们之前所探讨的那样,构造函数发生异常,运行期系统会寻找operator new相同的某个 operator delete 来回收内存,但是很可惜,它将不会找到对应的函数,所以就不会有任何版本的operator delete来调用。这就造成了内存泄漏。
相应的改善方法如下:
class Widget{ public: …… //其他如上例中 static void operator delete(void* pMemory,std::ostream &logStream) throw(); //非正常的delete形式,对应于 new 版本,防止内存泄漏 …… };
对于如上的类定义,在构造函数异常的时候就有了正确的版本进行delete的调用。也就不会造成隐蔽的内存泄漏。
3. 要考虑是否隐藏了operator new和operator delete的正常版本
C++在缺省情况下在global作用域内提供了三种形式的operator new。void* 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
这是C++98版本的,同样有C++11版本的,但是具体功能都差不多,在这里不关注这些问题。
如果我们在class内定义了自己的operator new和operator delete操作,那么根据C++的作用域规则,将会隐藏C++默认提供的global版本。
如下:
calss Base { ... public: static void *operator new(std::size_t size,std::ostream &logStream); //自定义operator new static void *operator delete(void *ptr,std::ostream &logStream); //自定义operator delete ... }
对于如上的Base,如果我们执行如下语句是OK的。
Base *pb = new(std::cerr) Base; //OK
但是想要使用C++缺省提供的new版本,就会出错
Base *pb = new Base; //错误,对应的缺省版本已经被隐藏
我们必须要考虑这种情况,在定义operator new和operator delete的时候,是否真的要隐藏默认版本不再使用。否则就需要设计相应的函数来保留这些函数。
一个做法是我们可以在class内定义与缺省版本相同参数的版本,并在函数内调用默认版本。
如下:
class Base { ... public: static void *operator new(std::size_t size) { ::operator new(size); //转调缺省版本 } //其他版本的operator new和operator delete ... };
这样子客户就依旧会拥有C++提供的operator new/delete 版本。
除此之外,Effective C++书中还提到建立一个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; //添加自己定义的operator new/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(); };
4. 总结
当编写一个placement operator new时,也要编写对应版本的placement operator delete。否则就可能造成隐蔽而时断时续的内存泄露。当声明了placement new和placement delete时,确定自己是否真的想要掩盖正常版本。
相关文章推荐
- effective C++ 条款 52:写了placement new也要写placement delete
- Effective C++ -----条款52:写了placement new 也要写 placement delete
- Effective C++ 条款 52:写了placement new也要写placement delete
- Effective C++ 条款52 写了placement new也要写placment delete
- 《Effective C++》读书笔记之item52:写了placement new也要写placement delete
- effective C++ 条款 52:写了placement new也要写placement delete
- 《Effective C++》:条款52:写了placement new也要写placement delete
- Effective C++:条款52 写了placement new也要写placement delete
- 读书笔记《Effective c++》 条款05 了解c++默默编写并调用哪些函数
- 读书笔记《Effective c++》 条款11 在operator= 中处理“自我赋值”
- 读书笔记《Effective c++》 条款13 以对象管理资源
- 读书笔记《Effective c++》 条款23 宁以non-member,non-friend替换member函数
- Effective C++ 读书笔记 条款1~2
- Effective C++ 读书笔记 条款一至三
- 【Effective C++】读书笔记 条款49~51
- 读书笔记《Effective C++》条款38:通过复合塑模出has-a或“根据某物实现出”
- 【Effective C++ 读书笔记】条款04:确定对象使用前已先被初始化
- 读书笔记《Effective C++》条款40:明智而审慎地使用多重继承
- 读书笔记《Effective C++》条款44:将与参数无关的代码抽离template
- Effective C++ 条款总结 读书笔记(二)