分析placement new

来源:互联网 发布:淘宝联盟如何跟聚划算 编辑:程序博客网 时间:2024/06/06 05:31

作者: Crazii @ CSDN

转载请说明出处

 

1.简单说明

 

通常我们先分配一块内存空间,然后可以反复使用用placemnet new 来将对象创建在这个内存块上,从而减少new时内存分配的开销,提高效率.

 笔者认为placement new 还有另一个用处,就是可以将对象创建在指定的内存上.(!-_-,这本来就是placemnet new 本分的工作),比如系统的内存有多个内存池的时候,可以指定创建在哪个池里面.

placement new和delete 的内容大致如下:

  1. voidnew(size_t size,void*__p)
  2. {
  3.     return __p;
  4. }
  5. void delete(void*,void*)
  6. {
  7. }

可以看出,placemnet new 只是直接返回了内存的地址,不没有做任何处理.delete更是什么都没做.

 

2.用法

使用placemnt new来创建一个对象:

 

 

  1. void* buffer = malloc(sizeof(OBJ));
  2. OBJ * obj = new(buffer) OBJ(param);//obj会在buffer中创建.

删除一个对象:

只销魂对象而保留内存时,可直接调用对象的析构函数.

 

 

  1. obj->~OBJ();

如果需要释放掉内存,就先调用析构函数,然后再释放掉那块内存

 

  1. obj->~OJB();
  2. free(buffer);

3.数组的情况

在单个对象的placement new时,通常buffer == obj,即obj的起始地址就是buffer的地址.

数组时的情况就有些复杂.看下面一段代码:

 

  1.  class A{ 
  2.   int n; 
  3.  public
  4.   A(){} 
  5.  }; 
  6. int main()
  7. {
  8.      void *p = malloc(sizeof(A)*2 );
  9.     A * a = new ( p) A[2];
  10.     a[0].~A();
  11.     a[1].~A();
  12.     free(p);
  13.     return 0;
  14. }

这里如果我们下一个断点,发现 p == a,说明对象数组a就在p开始的地方创建了.

 

但是稍稍改动一下,

 

  1.  class A{ 
  2.   int n; 
  3.  public
  4.   A(){} 
  5. ~A(){}
  6.  }; 

后面的没有任何修改,这时候再运行,发下 a == (int*)p + 1,即(intptr_t)a == (intptr_t)p + 4.

这就是我们所说的,在分配数组的时候,第一个4字节被用来保存数组中对象的个数了.

但是前面的情况为什么没有呢? 我想原因已经很清楚了.因为前面那个类没有显式声明析构函数.,编译器认为数组中的对象不需要调用析构函数,所以就把数组的大小给省掉了.

 

再看:

 

 

 

  1.  class A{ 
  2.   int n; 
  3.  std::set<inta_set;
  4.  public
  5.   A(){} 
  6.  }; 

这个时候把析构去掉了,但是加上了一个带有显式析构的成员:std::set<int>类型.笔者测试的结果是,前面仍然保留了数组的长度.

也就是说:如果这个类有显式的析构函数,或者它的成员中有对象需要显式析构,那么在分配数组的时候,就要保存数组的大小.这一点也很好理解.因为没有显式析构的对象,通常意味着它在被delete掉的时候,不需要做任何工作.所以编译器有理由不去调用析构函数.所以可以不用保存数组的大小,因为数组的大小就是用来析构单个对象时使用的.

经测试,普通的new,也是这样处理.

 

但是问题是,编译器这样的处理,让我们陷入了一个困境,那就是我们很难判断这两种情况,尤其是类的关系比较复杂的时候.

比如对于数组,我们本想写这样一个通用的宏,来调用数组中对象的析构:

 

 

  1. #define destroy_array(type,ptr) /
  2. do{/
  3.     if( ptr == NULL)/
  4.         break;/
  5.     for(int i=0; i < ((int*)ptr)[-1]; ++i)/
  6.     {/
  7.         ptr[i].~type();/
  8.     }/
  9.     free( (int*)ptr -1 );/        //如果需要释放内存,就加上这一句...
  10.     ptr = NULL;/
  11. }while(0)

对于数组的内存分配,我们可以加上一个sizeof(int)的大小,即使它没有被使用,这样也不是很浪费.

但是析构的时候,前面那个用来保存对象个数的int,可能是没有的....我们不得不写两个宏,来处理两种情况,或者,对每一个struct或者每一个类,都显示声明dctor,来保证数组前面一定有一个int...

 

3.继续讨论

另一个问题是,placement new 创建的对象,能不能delete 掉?

 

 

  1. void* buffer1 = malloc( sizeof(OBJ) );
  2. OBJ* obj = new(buffer1) OBJ(param);
  3. delete obj;
  4. void* buffer2 = malloc( sizeof(OBJ)*16 + sizeof(int) );
  5. OBJ* obj_a = new(buffer2) OBJ[16];
  6. delete[] obj_a;

先来看一下,默认的operator new,是调用malloc分配内存的.而这里我们的buffer,也是malloc分配的.

而对象的析构,由于数组前面有没有int来保存数组的大小,这编译器是知道的.(如同普通的new,delete和new[],delete[])

所以这种情况的结论是没有问题.可以使用..

所以,只要buffer的内存方式和new的一致,同delete对应,那么我们可以调用delete或者delete[]来释放数组.比如:

 

 

  1. void* operator new(size_t size)
  2. {
  3.     return my_special_malloc(size);
  4. }
  5. viod operator delete(void* ptr)
  6. {
  7.      my_special_free(ptr); //delete的内存释放, 与new 对应
  8. }
  9. ...
  10. void* buffer = my_special_malloc(sizeof(OBJ) ); //内存分配,与delete对应
  11. OBJ *obj = new(buffer) OBJ(param);
  12. delete obj;

代码在VC8 (VS05+SP1)上可以通过.

 

但是这样做buffer被立即删除,就不能重用了,但是至少这样能够方便地完成另一个目的,就是将内存分配到指定地位置....

原创粉丝点击