如果operator new接受的参数除了一定会有的那个std::size_t 之外还有其他,这便是所谓的placement new
众多placement new版本中特别有用的一个是“接受一个指针指向对象该被构造之处”,如下所示:
void* operator new(std::size_t size, void* pMemory) throw();
此版本的operator new已经在#include <new>中定义,功能之一就是:在vector的未使用空间上创建对象。这也正是placement new的命名规则:在一个特定位置上进行new。
有一点很重要的:当class中定义了placement new时,且使用了placement new进行了new class对象,你如果没有定义对应的placement new的话,当palcement new进行堆上创建class 对象时,构造函数抛出异常,此时!如果你没有定义placement delete,则会出现内存泄露!====>>>>>placement new为存放对象而申请的堆空间将由于没有placement delete可调用,将会出现内存泄漏!
placement new 和operator new的调用时机区别:
1)用placement new进行类的堆上对象创建时,当new成功申请到存放对象的堆内存后,在调用构造函数建立类对象来初始化堆内存时,如果构造函数出现错误抛出异常时,调用和placement new对应的placement delete,就是placement delete没有定义也不会调用operator new
2)delete pobj;时只会调用operator new,不会调用placement delete
所以没有定义placement delete导致的内存泄漏只会出现在:用placement new创建类的堆上对象过程中调用构造函数错误抛出异常时。
以下就是出现以上定义了placement new同时又定义了对应的placement delete,在创建堆上对象时不会出现内存泄漏的参照类测试代码。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <string>
#include <iostream>
#define PRINT_LINE_INFO \
do {\
printf("%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); \
}while(0);
class CDog
{
public:
CDog()
{
PRINT_LINE_INFO;
}
~CDog()
{
PRINT_LINE_INFO;
}
//< placement new
//< CDog *pdog = new(std::cerr) CDog();将会调用此placement new
void* operator new(std::size_t size, std::ostream& logstream) throw(std::bad_alloc)
{
printf("%s:%d===>placement new!\n", __FILE__, __LINE__);
std::size_t len = size == 0 ? 1 :size;
void* buf = malloc(len);
if (buf == NULL)
{
throw std::bad_alloc();
}
return buf;
}
//< placement delete
//< 只有!!!在调用placement new创建堆上对象时调用构造函数错误抛出异常时才会调用此placement delete
void operator delete(void* buf, std::ostream& logstream) throw()
{
printf("%s:%d===>placement delete!\n", __FILE__, __LINE__);
if (buf != NULL)
{
free(buf);
}
return;
}
//< operator new
//< CDog *pdog = new CDog();会调用此operator new
void* operator new(std::size_t size) throw(std::bad_alloc)
{
printf("%s:%d===>operator new!\n", __FILE__, __LINE__);
std::size_t len = size == 0 ? 1 : size;
void* buf = malloc(len);
if (buf == NULL)
{
throw std::bad_alloc();
}
return buf;
}
//< operator delete
//< 任何的delete ptr;都会调用此operator delete
//< 注意和placement delete的调用时机区别
void operator delete(void* buf) throw()
{
printf("%s:%d===>operator delete!\n", __FILE__, __LINE__);
if (buf != NULL)
{
free(buf);
}
return;
}
};
int main()
{
printf("***************CDog *dog = new(std::cerr) CDog();************************\n");
CDog *dog = new(std::cerr) CDog(); //由于多了一个入参std::cerr, 将会调用placement new
delete dog;//调用正常的operator delete
printf("\n*************CDog *dog = new CDog();***********************************\n");
CDog *bdog = new CDog(); //调用正常的operator new
delete bdog;//调用正常的operator delete
return 0;
}
测试结果如下所示:
本文章的核心内容:
1)什么是placement new/delete
2)placement delet只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。
3)对着一个指针ptr实行delete,绝对!!!不会调用placement delete,而是调用的时operator delete。
4)为了杜绝由于使用placement new导致出现内存泄漏,必须提供一个相对应的placement delete用于构造函数抛出异常,且placement delete的额外参数必须和 placement new的额外参数一样!!!!
5)为了杜绝由于使用placement new导致出现内存泄漏,也必须提供一个正常的operator delete用于构造函数无异常时。
如果遵守了4)和5)就不会再使用placement new创建类的堆上对象时而担心内存泄漏,可以保证构造函数是否抛出异常都能够有delete函数被调用来释放已经申请的存放类对象的堆内存buf。
(完)