color:rgb(85,85,85)"> placement new 是重载operator new的一个标准、全局的版本c;它不能被自定义的版本代替(不像普通的operator new和operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete能够被替换成用户自定义的版本)。color:rgb(255,0,0); font-family:Helvetica,Arial,sans-serif; text-align:left; font-size:18px">placement new的作用就是:创建对象(调用该类的构造函数)但是不分配内存c;而是在已有的内存块上面创建对象。用于需要反复创建并删除的对象上c;可以降低分配释放内存的性能消耗。
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 它的原型如下:
void *operator new( size_t, void *p ) throw() { return p; }
color:rgb(85,85,85)">
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 首先我们区分下几个容易混淆的关键词:new、operator new、placement new
new和class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete操作符我们应该都用过c;它们是对堆中的内存进行申请和释放c;而这两个都是不能被重载的。要实现不同的内存分配行为c;需要重载operator newc;而不是new和class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete。
color:rgb(85,85,85)"> 看如下代码:
class="tags" href="/tags/CLASS.html" title=class>class MyClass {…};
MyClass * p=new MyClass;
color:rgb(85,85,85)"> 这里的new实际上是执行如下3个过程:
color:rgb(85,85,85)"> 1. 调用operator new分配内存 ;2. 调用构造函数生成类对象;3. 返回相应指针。
color:rgb(85,85,85)"> operator new就像operator+一样c;是可以重载的c;但是不能在全局对原型为void operator new(size_t size)这个原型进行重载c;一般只能在类中进行重载。如果类中没有重载operator newc;那么调用的就是全局的::operator new来完成堆的分配。同理c;operator new[]、operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete、operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete[]也是可以重载的c;一般你重载的其中一个c;那么最后把其余的三个都重载一遍。
color:rgb(85,85,85)"> 至于placement new才是本文的重点。其实它也只是operator new的一个重载的版本c;只是我们很少用到它。如果你想在已经分配的内存中创建一个对象c;使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
color:rgb(85,85,85)"> 我们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间c;这个操作速度是很慢的c;而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行c;不需要查找内存c;内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以c;placement new非常适合那些对时间要求比较高c;长时间运行不希望被打断的应用程序。
color:rgb(85,85,85)"> 使用方法如下:
1. 缓冲区提前分配
可以使用堆的空间c;也可以使用栈的空间c;所以分配方式有如下两种:
class="tags" href="/tags/CLASS.html" title=class>class MyClass {…};
char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];
color:rgb(85,85,85)"> 2. 对象的构造
MyClass * pClass=new(buf) MyClass;
color:rgb(85,85,85)"> 3. 对象的销毁
一旦这个对象使用完毕c;color:rgb(255,0,0)">你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放c;以便其他的对象的构造。
pClass->~MyClass();
color:rgb(85,85,85)"> 4. 内存的释放
如果缓冲区在堆中c;那么调用class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete[] buf;进行内存的释放;如果在栈中c;那么在其作用域内有效c;跳出作用域c;内存自动释放。
color:rgb(85,85,85)">
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 注意:
color:rgb(85,85,85)"> 在C++标准中c;对于placement operator new []有如下的说明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数c;或者说数组的大小。使用方法第二步中的new才是placement newc;其实是没有申请内存的c;只是调用了构造函数c;返回一个指向已经分配好的内存的一个指针c;所以对象销毁的时候不需要调用class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete释放空间c;但必须调用析构函数销毁对象。
color:rgb(85,85,85)"> [使用方法]
color:rgb(85,85,85)"> 在很多情况下c;placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。
color:rgb(85,85,85); font-family:Consolas; font-size:14px; line-height:18px">第一步 缓存提前分配color:rgb(85,85,85)"> 为了保证通过placement new使用的缓存区的memory alignmen(内存队列)正确准备c;使用普通的new来分配它:
color:rgb(85,85,85)"> color:rgb(0,102,255)">class="tags" href="/tags/CLASS.html" title=class>class Task ;
color:rgb(85,85,85)"> color:rgb(0,102,255)">char * buff = new [sizeof(Task)]; color:rgb(0,102,0)">//分配内存
color:rgb(85,85,85)"> (请注意auto或者static内存并非都正确地为每一个对象类型排列c;所以c;你将不能以placement new使用它们。)
color:rgb(85,85,85); font-family:Consolas; font-size:14px; line-height:18px">第二步:对象的分配color:rgb(85,85,85)"> 在刚才已分配的缓存区调用placement new来构造一个对象。
color:rgb(85,85,85)"> color:rgb(0,102,255)">Task *ptask = new(buff) Task
color:rgb(85,85,85); font-family:Consolas; font-size:14px; line-height:18px">第三步:使用color:rgb(85,85,85)"> 按照普通方式使用分配的对象:
color:rgb(85,85,85)"> color:rgb(0,102,255)">ptask->suspend();
color:rgb(85,85,85)"> color:rgb(0,102,255)">ptask->resume();
color:rgb(85,85,85)"> color:rgb(0,102,0)">//...
color:rgb(85,85,85)">
color:rgb(85,85,85); font-family:Consolas; font-size:14px; line-height:18px">第四步:对象的毁灭color:rgb(85,85,85)"> 一旦你使用完这个对象c;你必须调用它的析构函数来毁灭它。按照下面的方式调用析构函数:
color:rgb(85,85,85)"> color:rgb(0,102,255)">ptask->~Task(); color:rgb(0,102,0)">//调用外在的析构函数
color:rgb(85,85,85)">
color:rgb(85,85,85); font-family:Consolas; font-size:14px; line-height:18px">第五步:释放color:rgb(85,85,85)"> 你可以反复利用缓存并给它分配一个新的对象(重复步骤2c;3c;4)如果你不打算再次使用这个缓存c;你可以象这样释放它:
color:rgb(85,85,85)"> color:rgb(0,102,255)">class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete [] buff;
color:rgb(85,85,85)"> 跳过任何步骤就可能导致运行时间的崩溃c;内存泄露c;以及其它的意想不到的情况。如果你确实需要使用placement newc;请认真遵循以上的步骤。
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 【Q & A】
color:rgb(85,85,85)"> 1、placement new 为何物?
color:rgb(85,85,85)"> placement new 是重载operator new 的一个标准、全局的版本c;它不能够被自定义的版本代替(不像普通版本的operator new 和 operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete能够被替换)。
color:rgb(85,85,85)"> void *operator new( size_t, void *p ) throw()
color:rgb(85,85,85)"> { return p; }
color:rgb(85,85,85)"> placement new的执行忽略了size_t参数c;只返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方c;达到调用构造函数的效果。
color:rgb(85,85,85)"> class="tags" href="/tags/CLASS.html" title=class>class SPort { ... }; // represents a serial port
color:rgb(85,85,85)"> const int comLoc = 0x00400000; // location of a port
color:rgb(85,85,85)"> //...
color:rgb(85,85,85)"> void *comAddr = reinterpret_cast<void *>(comLoc);
color:rgb(85,85,85)"> SPort *com1 = new (comAddr) SPort; // create class="tags" href="/tags/OBJECT.html" title=object>object at comLoc
color:rgb(85,85,85)"> com1->~SPort(); //释放
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 2、new 、operator new 和 placement new 一样吗?
color:rgb(85,85,85)"> new :不能被重载c;其行为总是一致的。它先调用operator new分配内存c;然后调用构造函数初始化那段内存。
color:rgb(85,85,85)"> operator new:要实现不同的内存分配行为c;应该重载operator newc;而不是new。
color:rgb(85,85,85)"> class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete和operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete类似。
color:rgb(85,85,85)"> placement new:只是operator new重载的一个版本。它并不分配内存c;只是返回指向已经分配好的某段内存的一个指针。因此不能删除它c;但需要调用对象的析构函数。
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 3、在已有的内存上用placement new分配数组
color:rgb(85,85,85)"> const int numComs = 4;
color:rgb(85,85,85)"> //...
color:rgb(85,85,85)"> SPort *comPorts = new (comAddr) SPort[numComs]; // create array
color:rgb(85,85,85)"> int i = numComs;
color:rgb(85,85,85)"> while( i )
color:rgb(85,85,85)"> comPorts[--i].~SPort();
color:rgb(85,85,85)">
color:rgb(85,85,85)"> 4、用Placement new 解决class="tags" href="/tags/BUFFER.html" title=buffer>buffer的问题
color:rgb(85,85,85)"> 用new分配的数组缓冲时c;由于调用了默认构造函数c;因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。用Placement new可以解决此类问题。
color:rgb(85,85,85)"> const size_t n = sizeof(class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string) * BUFSIZE;
color:rgb(85,85,85)"> class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string *sbuf = static_cast<class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string *>(::operator new( n ));
color:rgb(85,85,85)"> int size = 0;
color:rgb(85,85,85)"> //此时c;class="tags" href="/tags/BUFFER.html" title=buffer>buffer还没有初始化c;因此需要用 placement new 调用copy构造函数初始化。
color:rgb(85,85,85)"> void append( class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string buf[], int &size, const class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string &val )
color:rgb(85,85,85)"> { new (&buf[size++]) class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string( val ); } // placement new
color:rgb(85,85,85)"> //最后的清理
color:rgb(85,85,85)"> void cleanupBuf( class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string buf[], int size ) {
color:rgb(85,85,85)"> while( size )
color:rgb(85,85,85)"> buf[--size].~class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/STRING.html" title=string>string(); // destroy initialized elements
color:rgb(85,85,85)"> ::operator class="tags" href="/tags/CLASS.html" title=class>class="tags" href="/tags/DELETE.html" title=delete>delete( buf ); // free storage
color:rgb(85,85,85)"> }