作者: Crazii @ CSDN
转载请说明出处
1.简单说明
通常我们先分配一块内存空间,然后可以反复使用用placemnet new 来将对象创建在这个内存块上,从而减少new时内存分配的开销,提高效率.
笔者认为placement new 还有另一个用处,就是可以将对象创建在指定的内存上.(!-_-,这本来就是placemnet new 本分的工作),比如系统的内存有多个内存池的时候,可以指定创建在哪个池里面.
placement new和delete 的内容大致如下:
- void* new(size_t size,void*__p)
- {
- return __p;
- }
- void delete(void*,void*)
- {
- }
可以看出,placemnet new 只是直接返回了内存的地址,不没有做任何处理.delete更是什么都没做.
2.用法
使用placemnt new来创建一个对象:
- void* buffer = malloc(sizeof(OBJ));
- OBJ * obj = new(buffer) OBJ(param);//obj会在buffer中创建.
删除一个对象:
只销魂对象而保留内存时,可直接调用对象的析构函数.
- obj->~OBJ();
如果需要释放掉内存,就先调用析构函数,然后再释放掉那块内存
- obj->~OJB();
- free(buffer);
3.数组的情况
在单个对象的placement new时,通常buffer == obj,即obj的起始地址就是buffer的地址.
数组时的情况就有些复杂.看下面一段代码:
- class A{
- int n;
- public:
- A(){}
- };
- int main()
- {
- void *p = malloc(sizeof(A)*2 );
- A * a = new ( p) A[2];
- a[0].~A();
- a[1].~A();
- free(p);
- return 0;
- }
这里如果我们下一个断点,发现 p == a,说明对象数组a就在p开始的地方创建了.
但是稍稍改动一下,
- class A{
- int n;
- public:
- A(){}
- ~A(){}
- };
后面的没有任何修改,这时候再运行,发下 a == (int*)p + 1,即(intptr_t)a == (intptr_t)p + 4.
这就是我们所说的,在分配数组的时候,第一个4字节被用来保存数组中对象的个数了.
但是前面的情况为什么没有呢? 我想原因已经很清楚了.因为前面那个类没有显式声明析构函数.,编译器认为数组中的对象不需要调用析构函数,所以就把数组的大小给省掉了.
再看:
- class A{
- int n;
- std::set<int> a_set;
- public:
- A(){}
- };
这个时候把析构去掉了,但是加上了一个带有显式析构的成员:std::set<int>类型.笔者测试的结果是,前面仍然保留了数组的长度.
也就是说:如果这个类有显式的析构函数,或者它的成员中有对象需要显式析构,那么在分配数组的时候,就要保存数组的大小.这一点也很好理解.因为没有显式析构的对象,通常意味着它在被delete掉的时候,不需要做任何工作.所以编译器有理由不去调用析构函数.所以可以不用保存数组的大小,因为数组的大小就是用来析构单个对象时使用的.
经测试,普通的new,也是这样处理.
但是问题是,编译器这样的处理,让我们陷入了一个困境,那就是我们很难判断这两种情况,尤其是类的关系比较复杂的时候.
比如对于数组,我们本想写这样一个通用的宏,来调用数组中对象的析构:
- #define destroy_array(type,ptr) /
- do{/
- if( ptr == NULL)/
- break;/
- for(int i=0; i < ((int*)ptr)[-1]; ++i)/
- {/
- ptr[i].~type();/
- }/
- free( (int*)ptr -1 );/ //如果需要释放内存,就加上这一句...
- ptr = NULL;/
- }while(0)
对于数组的内存分配,我们可以加上一个sizeof(int)的大小,即使它没有被使用,这样也不是很浪费.
但是析构的时候,前面那个用来保存对象个数的int,可能是没有的....我们不得不写两个宏,来处理两种情况,或者,对每一个struct或者每一个类,都显示声明dctor,来保证数组前面一定有一个int...
3.继续讨论
另一个问题是,placement new 创建的对象,能不能delete 掉?
- void* buffer1 = malloc( sizeof(OBJ) );
- OBJ* obj = new(buffer1) OBJ(param);
- delete obj;
- void* buffer2 = malloc( sizeof(OBJ)*16 + sizeof(int) );
- OBJ* obj_a = new(buffer2) OBJ[16];
- delete[] obj_a;
先来看一下,默认的operator new,是调用malloc分配内存的.而这里我们的buffer,也是malloc分配的.
而对象的析构,由于数组前面有没有int来保存数组的大小,这编译器是知道的.(如同普通的new,delete和new[],delete[])
所以这种情况的结论是没有问题.可以使用..
所以,只要buffer的内存方式和new的一致,同delete对应,那么我们可以调用delete或者delete[]来释放数组.比如:
- void* operator new(size_t size)
- {
- return my_special_malloc(size);
- }
- viod operator delete(void* ptr)
- {
- my_special_free(ptr); //delete的内存释放, 与new 对应
- }
- ...
- void* buffer = my_special_malloc(sizeof(OBJ) ); //内存分配,与delete对应
- OBJ *obj = new(buffer) OBJ(param);
- delete obj;
代码在VC8 (VS05+SP1)上可以通过.
但是这样做buffer被立即删除,就不能重用了,但是至少这样能够方便地完成另一个目的,就是将内存分配到指定地位置....