在现有对象上使用 placement new 时如何定义对象的内容

How are contents of an object defined when using placement new on existing object

看下面的例子。 C++标准是否保证object.x的值最后等于1?如果我不调用析构函数怎么办 object.~Class();?

#include <new>

class Class
{
public:
  Class() {}
  ~Class() {}
  int x;
};

int main()
{
  Class object;
  object.x = 1;
  object.~Class();
  new (&object) Class();

  object.x == ?

  Class object_2;
  object_2.x = 1;
  new (&object_2) Class();

  object_2.x == ?

  return 0;
}

没有

x等于1的对象被销毁

你知道的,因为你是摧毁它的人。

您的新 x 未初始化且具有未指定的值,由于内存重用,实际上可能是 1。这与任何其他未初始化的值没有什么不同。


更新

因为似乎有很多人在争论断言,这里有一些 事实,直接来自标准。

没有关于这种情况的直接说明,因为它遵循管理对象是什么以及对象生命周期意味着什么的一般规则。

一般来说,一旦您理解了 C++ 是一个抽象 而不是字节的直接映射,您就会明白这里发生了什么,以及为什么没有这样的保证正如 OP 所寻求的那样。

首先,关于对象生命周期和销毁的一些背景知识:

[C++14: 12.4/5]: A destructor is trivial if it is not user-provided and if:

  • the destructor is not virtual,
  • all of the direct base classes of its class have trivial destructors, and
  • for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

Otherwise, the destructor is non-trivial.

[C++14: 3.8]: [..] The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.

[C++14: 3.8/3]: The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..]

[C++14: 3.8/4]: A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

(这段话的大部分内容与您的第一个案例无关,因为它 确实 有一个明确的析构函数调用:您的第二个案例违反了本段中的规则,因此具有未定义的行为;不再赘述。)

[C++14: 12.4/2]: A destructor is used to destroy objects of its class type. [..]

[C++14: 12.4/11]: [..] Destructors can also be invoked explicitly.

[C++14: 12.4/15]: Once a destructor is invoked for an object, the object no longer exists. [..]

现在,一些细节。如果我们要在 placement-new 之前检查 object.x 怎么办?

[C++14: 12.7/1]: [..] For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

哇,好的。

如果我们在放置新品后检查它会怎样?即新对象中的 x 值是多少?正如问题所问,它是否保证会是 1?请记住,Class 的构造函数不包含 x:

的初始化程序

[C++14: 5.3.4/17]: A new-expression that creates an object of type T initializes that object as follows:

  • If the new-initializer is omitted, the object is default-initialized (8.5); if no initialization is performed, the object has indeterminate value.
  • Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.

[C++14: 8.5/16]: The initialization that occurs in the forms

T x(a);
T x{a};

as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.

[C++14: 8.5/17]: The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined. [..]

  • If the initializer is (), the object is value-initialized.
  • [..]

[C++14: 8.5/8]: To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized.
  • [..]

[C++14: 8.5/7]: To default-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution (13.3) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
  • if T is an array type, each element is default-initialized; — otherwise, no initialization is performed.

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

[C++14: 12.6.2/8]: In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then:

  • if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
  • otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
  • otherwise, the entity is default-initialized (8.5).

[C++14: 8.5/12]: If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value.

那么,标准是否保证您的替换对象的 x 具有值 1?不,不是。

在实践中,为什么不能呢?好吧,有很多原因。 Class 的析构函数非常重要,因此根据 3.8,第一个对象的生命周期在您调用其析构函数后立即结束。

从技术上讲,只要在 placement-new 生效时对象被销毁,编译器就可以自由地将对象放置在该位置。它没有理由在这里这样做,但也没有什么禁止的;更重要的是,在析构函数调用和 placement-new 之间的简单 { int x = 5; x = 42; } 将更有资格重新使用该内存。那时它还没有被用来表示任何对象!

更实际的是,有些实现(例如 Microsoft Visual Studio)对于调试模式程序,将可识别的位模式写入未使用的堆栈内存以帮助诊断程序错误。没有理由认为这样的实现不会挂钩到析构函数中来执行此操作,并且这样的实现将覆盖您的 1 值。标准中没有任何内容禁止这样做。

确实,如果我用 std::cout 语句替换代码中的 ? 行,以便我们 实际上 检查值,我会收到警告关于未初始化的变量和值 0 在您使用析构函数的情况下:

main.cpp: In function 'int main()':
main.cpp:19:23: warning: 'object.Class::x' is used uninitialized in this function [-Wuninitialized]
   std::cout << object.x << '\n';
                       ^
0
1

Live demo

我不确定您还需要多少证据来证明标准不保证此处的 1 值。