非指针变量和类成员上的新放置

Placement new on non-pointer variables and class members

本文关键字:新放 成员 指针 变量      更新时间:2023-10-16

请考虑以下示例:

#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;
}

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

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

问题的第一部分围绕着这种方法的"合法性"。我发现了这个堆栈溢出问题,其公认的答案是

这是完全合法的。 而且没用,因为你不能使用 var [ 在这种情况下A a] 来指代你在放置 new 后存储在其中的 [对象] 的状态。任何此类访问都是未定义的行为。 [...]在任何情况下,您都不能在放置新的VAR之后引用它。

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

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

根据我的理解,使用A a还是A *b来访问对象应该无关紧要,因为放置 new 替换了A a地址处的对象,这当然A a。也就是说,我希望总是b == &a.也许答案不够清楚,这种限制是由于类成员的恒定性。

这是另一个具有相同想法的示例,但是这次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
}

问题基本相同,推理相同。将新放入地址&a是否有效?

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

如果在对象的生存期结束后,在重新使用或释放该对象所占用的存储之前,则会在原始对象占用的存储位置创建一个新对象,指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用新对象并且,一旦新对象的生存期开始,可用于操作新对象,如果:

  • 新对象的存储完全覆盖原始对象占用的存储位置,并且

  • 新对象与原始对象的类型相同(忽略顶级 CV 限定符(,并且

  • 原始对象的类型不是常量限定的
  • ,如果是类类型,则不包含任何类型为常量限定或引用类型的非静态数据成员,并且

  • 原始对象和新对象都不是可能重叠的子对象 ([intro.object](。

强调我的

您检查所有这些要求,以便a引用您放置在a内存中的"新"A