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

use*_*657 6 c++ oop standards language-lawyer

看下面的例子.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;
}
Run Code Online (Sandbox Code Playgroud)

Lig*_*ica 12

没有.

x等于的对象1被销毁.

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

您的新x内容未初始化且具有未指定的值,这可能是1由于内存重复使用而导致的.这与任何其他未初始化的价值没有什么不同.


更新

由于似乎有很多人抛出断言,这里有一些事实,直接来自标准.

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

通常,一旦你认为C++是抽象而不是字节的直接映射,你就可以理解这里发生了什么,以及为什么没有OP寻求的这种保证.

首先,关于对象生命周期和破坏的一些背景:

[C++14: 12.4/5]: 如果不是用户提供的析构函数是微不足道的,如果:

  • 析构函数不是virtual,
  • 它的所有直接基类都有琐碎的析构函数
  • 对于类类的所有非静态数据成员(或其数组),每个这样的类都有一个简单的析构函数.

否则,析构函数是非平凡的.

[C++14: 3.8]:[..] 类型对象的生命周期T结束时:

  • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
  • 对象占用的存储器被重用或释放.

[C++14: 3.8/3]:本国际标准中归属于对象的属性仅在其生命周期内适用于给定对象.[..]

[C++14: 3.8/4]:程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期. 对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数 ; 但是,如果没有显式调用析构函数或者如果没有使用delete-expression(5.3.5)来释放存储,则不应该隐式调用析构函数,并且任何程序都依赖于析构函数产生的副作用有未定义的行为.

(这段话的大部分内容与你的第一种情况无关,因为它确实有一个明确的析构函数:你的第二种情况违反了本段中的规则,因此具有未定义的行为;不应进一步讨论.)

[C++14: 12.4/2]:析构函数用于销毁其类类型的对象.[..]

[C++14: 12.4/11]:[..]也可以显式调用析构函数.

[C++14: 12.4/15]:一旦为对象调用析构函数,该对象就不再存在.[..]

现在,一些具体细节.如果我们object.x在安置之前检查怎么办?

[C++14: 12.7/1]:[..] 对于具有非平凡析构函数的对象,在析构函数完成执行后引用对象的任何非静态成员或基类会导致未定义的行为.

哇,好的.

如果我们在安置后检查它怎么办?即x新对象中的值是多少?正如问题所问,它是否会得到保证,它会是1什么?请记住,Class构造函数不包含以下内容的初始化x:

[C++14: 5.3.4/17]:新表达式创建类型的对象T如下初始化该对象:

  • 如果省略new-initializer,则默认初始化对象(8.5); 如果没有执行初始化,则该对象具有不确定的值.
  • 否则,根据8.5的初始化规则解释new-initializer以进行直接初始化.

[C++14: 8.5/16]: 表单中发生的初始化

T x(a);
T x{a};
Run Code Online (Sandbox Code Playgroud)

new表达式(5.3.4)中,static_cast表达式(5.2.9),函数表示法类型转换(5.2.3)以及基本和成员初始化器(12.6.2)称为直接初始化.

[C++14: 8.5/17]:初始化器的语义如下.目标类型是要初始化的对象或引用的类型,源类型是初始化表达式的类型.如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型.[..]

  • 如果初始化程序是(),则对象进行值初始化.
  • [..]

[C++14: 8.5/8]:对值初始化类型的对象T意味着:

  • 如果T是一个(可能是cv限定的)类类型(第9节),没有默认构造函数(12.1)或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的.
  • [..]

[C++14: 8.5/7]: 默认初始化类型对象T意味着:

  • 如果T是一个(可能是cv限定的)类类型(第9节),则调用默认构造函数(12.1)T(如果T没有默认构造函数或重载解析(13.3),则初始化是错误的,导致歧义或在从初始化的上下文中删除或无法访问的函数);
  • 如果T是数组类型,则每个元素都是默认初始化的; - 否则,不执行初始化.

如果程序要求对const限定类型的对象进行默认初始化T,T则应为具有用户提供的默认构造函数的类类型.

[C++14: 12.6.2/8]: 在非委托构造函数中,如果给定的非静态数据成员或基类未由mem-initializer-id指定(包括没有mem-initializer-list的情况,因为构造函数没有ctor-initializer)并且实体不是抽象类的虚拟基类(10.4),然后:

  • 如果实体是具有大括号或等于初始值的非静态数据成员,则按照8.5中的规定初始化该实体;
  • 否则,如果实体是匿名联合或变体成员(9.5),则不执行初始化;
  • 否则,实体默认初始化(8.5).

[C++14: 8.5/12]:如果没有为对象指定初始化程序,则默认初始化该对象; 如果未执行初始化,则具有自动或动态存储持续时间的对象具有不确定的值.

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

在实践中,为什么不是呢?好吧,有很多原因.Class析构函数是非平凡的,因此,根据3.8,第一个对象的生命周期在你调用它的析构函数后立即结束.

从技术上讲,编译器可以自由地在该位置放置一个对象,只要它在placement-new生效时被销毁.这里没有理由这样做,但没有任何禁止它的东西; 更重要的是,{ int x = 5; x = 42; }析构函数调用和placement-new之间的简单介绍将超过有权重用该内存的权限.它并没有被用来代表任何对象!

更现实地,有一些实现(例如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
Run Code Online (Sandbox Code Playgroud)

现场演示

我不确定你需要多少证据证明标准不能保证1这里的价值.