C++笔记(8)常规new运算符和定位new运算符
通常,new负责在堆(heap)中找到一个能够满足要求的内存块。new运算符还有一种变体,被称为定位(placement)new运算符,他能让你能够指定要使用的位置。程序员可以使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
要使用定位new特性,
- 需要包含头文件new,它提供了这种版本的new运算符的原型;
- 将new运算符用于提供所需地址的参数。
下面的代码段演示了new运算符的4种用法:
#include <new> struct chaff { char dross[20]; int slag; }; char buffer1[50]; char buffer2[500]; int main() { chaff* p1, * p2; int* p3, * p4; //常规new运算符 p1 = new chaff;//结构放在堆中 p3 = new int[20];//int数组放在堆中 //定位new运算符 p2 = new (buffer1) chaff;//结构放在buffer1中 p4 = new (buffer2) int[20];//int数组放在buffer2中 return 0; }
上面这个示例使用两个静态数组来为定位new运算符提供内存空间。接下来使用常规new运算符和定位new运算符创建动态分配的数组。
#include <iostream> #include <new> const int BUF = 512; const int N = 5; char buffer[BUF]; int main() { using namespace std; double* pd1, * pd2; int i; cout << "Calling new and placement new:\n"; pd1 = new double[N]; pd2 = new (buffer) double[N]; for (i = 0; i < N; i++) { pd2[i] = pd1[i] = 1000 + 20.0 * i; } cout << "Memory adress:\n" << "heap:" << pd1 << " static:" << pd2 << endl; cout << "Memory contents:\n"; for (i = 0; i < N; i++) { cout << pd1[i] << " at " << pd1 + i << ";"; cout << pd2[i] << " at " << pd2 + i << endl; } //----------------again--------------------------- cout << "Calling new and placement new a second time:\n"; double* pd3, * pd4; pd3 = new double[N]; pd4 = new (buffer) double[N]; for (i = 0; i < N; i++) { pd4[i] = pd3[i] = 1000 + 40.0 * i; } cout << "Memory adress:\n" << "heap:" << pd3 << " static:" << pd4 << endl; cout << "Memory contents:\n"; for (i = 0; i < N; i++) { cout << pd3[i] << " at " << pd3 + i << ";"; cout << pd4[i] << " at " << pd4 + i << endl; } //释放p1指向的内存块,在buffer中分配新的内存 cout << "Calling new and placement new a third time:\n"; delete[] pd1; pd1 = new double[N]; pd2 = new (buffer + N * sizeof(double)) double[N]; for (i = 0; i < N; i++) { pd2[i] = pd1[i] = 1000 + 60.0 * i; } cout << "Memory adress:\n" << "heap:" << pd1 << " static:" << pd2 << endl; cout << "Memory contents:\n"; for (i = 0; i < N; i++) { cout << pd1[i] << " at " << pd1 + i << ";"; cout << pd2[i] << " at " << pd2 + i << endl; } delete[] pd1; delete[] pd3; }
运行结果:
注意:
1. p2和buffer的地址相同,说明确实将数组p2放在数组buffer中了。p1位于动态管理的堆中。
2. 由于buffer是char指针,将cout运用于char指针时,会从第一个字符开始打印,直到遇到空字符为止。因此使用(void *)对 buffer进项强制转化。
3. 定位new运算符使用传递给它的地址,不跟踪那些内存单元被使用,也不查找未使用的内存块。因此,第二次定位new运算符分配了与第一次相同的内存块。这将一些内存管理的负担交给了程序员。
//偏移量为40bytes pd2 = new (buffer + N * sizeof(double)) double[N];
4. 用定位new运算符来创建新的类对象后,当该对象消亡时,程序并不会自动地调用其析构函数,所以必须显式地调用析构函数。这个少数的需要显示调用析构函数的情况之一。
上面的程序中没有用delete来释放定位new运算符分配地内存。buffer指定的内存是静态内存,而delete只用用于这样的指针:指向常规new分配的堆内存。也就是说,数组buffer位于delete的管辖区域之外,下面的语句将引发运行阶段错误:
delete [] pd2;//won't work
如果buffer是使用常规new运算符创建的,便可使用常规delete运算符来释放整个内存块。
char * buffer = new char[BUF]; delete [] buffer;
delete [] buffer;释放使用常规new运算符分配的整个内存块,但没有为定位new运算符在该内存块中创建的对象调用析构函数。
这种问题的解决方法是,显式地为使用定位new运算符创建的对象调用析构函数。显式地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:
String *pd = new (buffer) String("hello",5); pd->~String();
需要注意的是,对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区。
5. new运算符只是返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。