简介
一般的new运算符负责在heap堆中找到一个足以能够满足要求的内存块。
new运算符还有另一种变体:定位new运算符(placement new),它能够让程序员指定要使用的位置。既将new运算符用于提供了的地址。
定位new运算符在头文件new中。
定位new运算符直接使用传递给它的地址,它不负责判断哪些内存单元已被使用,也不查找未使用的内存块。这将一些内存管理的负担交给了程序员。
一个例子:
下面用一个简单的程序来说明定位new的用法:
#include <new>
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
//chunk of memory内存池
char * buffer=(char *)malloc(512*sizeof(char));
int *p1, *p2, *p3, *p4;
//常规new:
p1 = new int[10];
//定位new:
p2 = new (buffer) int[10];
for (int i = 0; i < 10; ++i)
p1[i] = p2[i] = 20 - i;
cout << "p1 = " << p1 << endl; //常规new指向的地址
cout << "buffer = " << (void *)buffer << endl; //内存池地址
cout << "p2 = " << p2 << endl; //定位new指向的地址
cout << "p2[0] = " << p2[0] << endl;
delete []p1;
p3 = new (buffer) int;
*p3 = 1;
cout << "p3 = " << p3 << endl;
cout << "p3[0] = " << *p3 << endl;
p4 = new (buffer + 10 * sizeof(int)) int;
cout << "p4 = " << p4 << endl;
cout<<"p2:"<<p2<<" p3:"<<p3<<" p4:"<<p4<<endl;//定位new的地址,都处于内存池中
cout<<"p2:"<<*p2<<" p3:"<<*p3<<" p4:"<<*p4<<endl;//值也符合预期
delete buffer;//释放内存池,定位new出来的指针不用delete,也不能delete
cout<<"p2:"<<p2<<" p3:"<<p3<<" p4:"<<p4<<endl;//地址不变
cout<<"p2:"<<*p2<<" p3:"<<*p3<<" p4:"<<*p4<<endl;//但内容已经是内存垃圾
return 0;
}
输出结果:
注:为了方便,这里使用malloc提供内存空间。
可以看到,第一次使用定位new时,p2直接使用了我们显示供给的内存。第二次使用定位new时,程序还是直接使用了我们提供的地址,不管它是否已经被使用,而且可以看到新值直接覆盖在旧值上面。
第三次使用定位new时,由于我们提供了相对于buffer的偏移量,所以新的指针指向的地址与buffer首地址偏移了10个int字节。
另外一点要说明的是,不同与常规的new运算符,定位new运算符不需要相应的delete运算符来释放内存。因为它本身就不开辟新的内存。只是在开辟好的指定的内存地址上调用相应的构造函数去构造对象。
工作原理:
简单来说就是定位new运算符只是返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。
隐患:
用将定位new运算符来创建新的类对象后,当该对象消亡时,程序并不会自动地调用其析构函数,所以必须显示地调用析构函数。这是少数的需要显示调用析构函数的情况之一。
这里使用了内置类型 int,不需要调用析构函数。若是对象,须显示调用析构函数。
需要注意的是,对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区。