zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

C++笔记(8)常规new运算符和定位new运算符

2023-02-18 16:27:15 时间

  通常,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 *,以便能够赋给任何指针类型。