这个“second new”是什么?

14

第二行是什么?(在回答另一个问题时看到。)

int * x = new int [1] ;
int * y = new (x) int;

第二行代码执行后,x和y将拥有相同的值(指向同一个位置)。那么y = x和这段代码的区别是什么?这像是一种构造函数还是其他什么东西吗?


注意:通常不会使用placement new与int一起使用(因为它没有优势(int没有构造函数))。 - Martin York
@Martin:虽然 new(x) int() 会有所不同,但可以通过模板使用。即使是在使用 new[] 时,始终使用括号也是一个好习惯。 - Roger Pate
提供你在看到这个问题的另一个链接可能会有帮助。 - Roger Pate
5个回答

13

这是 定位 new。 它在指向 x 的内存中构造一个新的 int

如果你尝试:

int * x = new int [1];
*x = 5;
std::cout << *x << std::endl;
int * y = new (x) int;
*y = 7;
std::cout << *x << std::endl;

输出将为:

5
7

@Green Code:没错。Placement new 只是执行初始化操作。就像在给定的分配内存上调用该类型的构造函数一样。 - Matteo Italia
4
对于基本数据类型,实际上没有什么区别。对于类类型,存在很大的区别,因为构造函数将再次被调用。 - Oliver Charlesworth
这就是我一直在寻找的东西;)另外,您对我的一个问题投了反对票,当时我指的是继承或基类中的父项。我已经进行了编辑,您可以取消反对票:D,谢谢! - Amir Zadeh
1
@Geen Code:Placement new通常不会像这样使用。请查看以下内容。 - Martin York
1
请记住,new(x) int != new(x) int() - Roger Pate
显示剩余3条评论

4

我不完全理解!当我们再次 new 一个东西时,它的指针会改变!但是如果不改变指针该怎么办!请看 VCEXPRESS8 的输出:第二行之前的 x 为 0x005c4e58,第二行之后的 x 也是 0x005c4e58,它们是一样的! - Amir Zadeh
1
它告诉编译器:“执行通常的构造函数,但是不要创建更多的内存,而是使用在x处的内存。”因此,它不会修改x,而是覆盖了你在第一行创建的int - Jonathan Grynspan
@Green,使用放置new时没有分配新的内存,这就是为什么你示例中的y指向与x相同的内存地址。可以将其视为省略了内存分配步骤,直接使用提供的内存块立即执行适当的构造函数调用。 - Péter Török
所以,如果我理解正确的话,最终得到的是指向同一内存块中结构体的2个指针?那么,为什么要使用X和Y呢?为什么不只使用X? - EKI
1
@EKI,上面的示例使用了int类型,在这种情况下没有太大的区别。然而,对于非原始类型,可能会有很大的区别。例如,您可以分配一个适当大小的char数组,然后使用放置new将一个YourFavouriteType对象初始化到其中。然后,您就有了一个指向物理上相同内存的char*YourFavouriteType*,但是它们“看到”的可能是非常不同的东西。 - Péter Török
@EKI,此外,您可以将几个不同的对象初始化为相邻地址,在同一个大内存缓冲区内 - 这基本上是自定义分配器所能做的。 - Péter Török

3
这是放置新对象。
虽然您通常不会将其用于整数类型。
它通常用于构建缓冲区,然后在其中构建其他类型。
// Allocate a buffer with enough room for two T objects.
char* buffer   = new char[sizeof(T) * 2];

// Allocate a T in slot zero
T* t1 = new (buffer + 0 * sizeof(T)) T("Zero");

// Allocate a T in slot one
T* t2 = new (buffer + 1 * sizeof(T)) T("One");

这是基础知识。
但请记住,使用放置 new 分配的对象不能使用delete语句删除。这是因为delete试图回收由new分配的内存(以及调用析构函数)。因此,要正确使用这些对象,必须手动调用它们的析构函数。

t1->~T();
t2->~T();

不要忘记删除原始缓冲区。

delete [] buffer;

一些需要注意的事项:
人们常常认为缓冲区可以在堆栈上实现,因此可以自动释放。
char buffer[sizeof(T) * 2];

很遗憾,这在技术上可能是可以的(它可以编译)。但不能保证缓冲区的内存正确对齐以便于放置T。所以你必须动态分配缓冲区(通过使用new,它保证了为分配大小的任何对象正确对齐的内存(因此按扩展也对分配大小小于分配大小的任何对象正确对齐)。解决此问题的简单方法是使用std::vector

std::vector<char>    buffer(sizeof(T) * 2);
T* t1 = new (&buffer[0] + 0 * sizeof(T)) T("Zero");
T* t2 = new (&buffer[0] + 1 * sizeof(T)) T("One");

使用放置new的另一个用途是重置对象。
我见过这样做,但我更喜欢使用更标准的赋值运算符:

T   obj1("Plop");
obj1  = T("Another Plop");

// Can be done like this:
T   obj1("Plop");
obj1.~T();
new (&obj1) T("Another Plop");   // Seems excessive to me. But can be us-full
                                 // in some extreme situations.

请记住,如果您使用重置方法,必须先销毁旧对象(否则对象可能无法正确运行)。


3
第二个新功能是“放置new”。它可以在不进行任何分配的情况下执行初始化(即调用任何必要的构造函数)。当需要创建自定义内存分配方案时,它非常有用。

2
int * y = new (x) int; 

这是使用放置new语法实现的。

编辑:除了自定义分配,放置new还可以帮助重新初始化对象状态,如下所示。

class Test
{
    int startVal;
public:
    Test()
    {
        startVal = 1;
    }
    void setVal(int val) { startVal = val; }
};
int main()
{
    Test *p = new Test;  //Creates new object and initializes it with
                          //a call to constructor.
    p->setVal(10);  //Change object content.
    new(p) Test; //Reset object:
    //object pointed by p will be re-initialzed here by making
    //a call to constructor. startVal will be back to 1
}

如上面的评论所述,对象状态重置也可以使用placement new实现。placement new不会分配内存,而是在括号中指定的地址构造对象。

这不是一个好主意。如果Test包含指针成员,会发生什么情况呢?因为您没有在原始对象上调用析构函数,所以可能会泄漏内存。如果您要使用放置new来重置值(通常不是一个好主意),您必须首先调用对象的析构函数(您可以通过指针手动调用析构函数)。 - Martin York
@Martin:说得好。但是我在我们广泛使用的商业代码中看到了“re-setting”的使用。它们在类内部没有任何指针。不确定这是否是不良实践。无论如何,谢谢。 - bjskishore123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接