定位new表达式

默认情况下,如果new不能分配说需要的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常:

//如果分配失败,new返回一个空指针
int* p1 = new int;    //如果分配失败,new抛出std::bad_alloc
int* p2 = new (nothrow) int;     //如果分配失败,new返回一个空指针。

我们称这种形式的new为定位new

定位new表达式允许我们向new传递额外的参数。如上面的nothrow。如果将nothrow传递给new.我们的意图是告诉它不能抛出异常。如果这种形式的new不能分配所需内存,它会返回一个空指针。

在c++早期版本中,有一个allocator类。其中的成员函数allocate负责分配内存空间,deallocate用来释放内存空间。但这两个函数不会构造或销毁对象。而是由construct函数来构造对象,用destroy函数来释放对象。

对于operator new分配的内存来说,我们无法用construct函数构造对象。相反,我们应该用new的定位new形式构造对象。

定位new的形式:

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] {braced initializer list}

其中place_address必须是一个指针。同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表。该初始值列表将用于构造分配的对象。

定位new允许我们在一个特定的,预先分配的内存地址上构造对象。

但仅通过一个地址值调用时,定位new使用operator new(size_t, void*)“分配”它的内存。这是一个我们无法自定义的operator new版本。该函数不分配任何内存,它只是简单滴返回指针实参,然后由new表达式负责在指定的地址初始化对象以完成整个工作。

下面是博客 https://blog.csdn.net/shujh_sysu/article/details/51925426 里面讲到的。

一般的new运算符负责在堆中找到一个足以能够满足要求的内存块。

new运算符还有另一种变体:定位new运算符(placement new),它能够让程序员指定要使用的位置。即将new运算符用于已经提供了的地址。

定位new运算符直接使用传递给它的地址,它不负责判断哪些内存单元已被使用,也不查找未使用的内存块。这将一些内存管理的负担交给了程序员。
 

下面来看一个例子

#include "new"
#include "iostream"
using namespace std;
int main() {
    char buffer[512];   //chunk of memory内存池
    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;
    return 0;
}

输出

注:为了方便,这里使用一个静态数组来为定位new提供内存空间。

可以看到,第一次使用定位new时,p2直接使用了我们显示供给的内存。第二次使用定位new时,程序还是直接使用了我们提供的地址,不管它是否已经被使用,而且可以看到新值直接覆盖在旧值上面。

第三次使用定位new时,由于我们提供了相对于buffer的偏移量,所以新的指针指向的地址与buffer首地址偏移了10个int字节。

另外一点要说明的是,不同与常规的new运算符,定位new运算符不需要相应的delete运算符来释放内存。因为它本身就不开辟新的内存。

工作原理:
简单来说就是new运算符只是返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。

隐患:
用将定位new运算符来创建新的类对象后,当该对象消亡是时,程序并不会自动地调用其析构函数,所以必须显示地调用析构函数。这个少数的需要显示调用析构函数的情况之一。

需要注意的是,对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区
 

猜你喜欢

转载自blog.csdn.net/ypshowm/article/details/89251682