在非指针变量和 class 成员上放置 new

Placement new on non-pointer variables and class members

考虑以下示例:

#include <iostream>

struct A {

    int i;

    A(int i)
    {
        this->i = i;
    }

    A &operator=(const A &a) = delete;
    A(const A &a) = delete;
};

int main()
{
    A a(1);
    new(&a) A(5);
    //a = A(7); // not allowed since = is deleted in A
    std::cout << a.i << std::endl;
}

这是一个使用放置新运算符的简单示例。由于 struct A 的复制构造函数和赋值运算符已被删除(无论出于何种原因),因此无法更改变量 A a 持有的对象,除非将其地址传递给放置新运算符。

这样做的原因可能包括 struct A 包含大型数组(例如 100M 条目),这些数组必须在赋值运算符和复制构造函数中进行复制。

问题的第一部分围绕着这种方法的 "legality"。我找到了 Whosebug 问题,接受的答案是

this is perfectly legal. And useless, because you cannot use var [A a in this case] to refer to the state of the [object] you stored within it after the placement new. Any such access is undefined behavior. […] under no circumstance may you ever refer to var after you placement new'd over it.

为什么会这样?我已经看到了放置 new 运算符的其他几个示例,它们总是类似于

A a(1);
A *b = new(&a) A(2);
// Now use *b instead of a

根据我的理解,使用 A aA *b 来访问对象应该无关紧要,因为新放置会替换 A a 地址处的对象,当然 A a。也就是说,我希望 总是 b == &a。也许答案不够清楚,这个限制是由于 class 成员的 const-ness。

这是另一个具有相同想法的示例,但是这次 struct A 嵌入到另一个对象中:

#include <iostream>

struct A {

    int *p;

    A(int i)
    {
        p = new int(i);
    }

    ~A()
    {
        delete p;
    }

    A &operator=(const A &a) = delete;
    A(const A &a) = delete;
};

struct B {

    A a;

    B(int i) : a(i)
    {
    }

    void set(int i)
    {
        a.~A(); // Destroy the old object
        new(&a) A(i);
    } 

};

int main()
{
    B b(1);
    b.set(2);
    std::cout << *(b.a.i) << std::endl;
    // This should print 2 and there should be no memory leaks
}

题目基本相同,同理。将 placement-new 放入地址 &a 是否有效?

对于这个特定的代码,您可以使用 a 来引用您放置在其位置的新对象。 [basic.life]/8

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and

  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and

  • neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).

强调我的

您勾选了所有这些要求,因此 a 将引用您放置在 a 内存中的“新”A