在现有对象上使用新放置时,如何定义对象的内容
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]:
如果析构函数不是用户提供的,并且如果:
- 析构函数不是CCD_ 9
- 其类的所有直接基类都有琐碎的析构函数,并且
- 对于类类型(或其数组)的类的所有非静态数据成员,每个此类都有一个平凡的析构函数
否则,析构函数是非平凡的
[C++14: 3.8]
:[..]类型为T
的对象的生存期在以下情况下结束:
- 如果
T
是一个具有非平凡析构函数(12.4)的类类型,则析构函数调用启动,或者- 对象占用的存储器被重新使用或释放
[C++14: 3.8/3]
:本国际标准中赋予对象的属性仅适用于给定对象的使用寿命[..]
[C++14: 3.8/4]
:程序可以通过重用对象占用的存储,或者通过显式调用具有非平凡析构函数的类类型对象的析构函数,来结束任何对象的生存期对于具有非平凡析构函数的类类型的对象,在对象占用的存储被重用或释放之前,程序不需要显式调用析构函数;然而,如果没有对析构函数的显式调用,或者没有使用delete表达式(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]
:[..]对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类会导致未定义的行为
哇,好吧。
如果我们在放置新的之后检查它呢?即,新对象中的CCD_ 20保持什么值?正如问题所问,它是否保证会是1
?请记住,Class
的构造函数不包括x
:的初始化程序
[C++14: 5.3.4/17]:
创建T
类型对象的新表达式将该对象初始化如下:
- 如果省略新初始值设定项,则默认初始化对象(8.5);如果不执行初始化,则对象的值不确定
- 否则,新初始值设定项将根据8.5的初始化规则进行解释,用于直接初始化
[C++14: 8.5/16]:
表单中发生的初始化T x(a); T x{a};
以及在
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条),则T
的默认构造函数(12.1)被称为(如果T
没有默认构造函数或重载解析(13.3)导致模糊性或在从初始化的上下文中被删除或不可访问的函数中)- 如果
T
是数组类型,则默认初始化每个元素;--否则,不执行初始化如果程序调用常量限定类型
T
的对象的默认初始化,则T
应为具有用户提供的默认构造函数的类类型。
[C++14: 12.6.2/8]:
在非委托构造函数中,如果给定的非静态数据成员或基类不是由mem初始值设定项id指定的(包括由于构造函数没有ctor初始值设定项),并且实体不是抽象类的虚拟基类(10.4),则:
- 如果实体是具有大括号或相等初始值设定项的非静态数据成员,则实体将按照8.5中的规定进行初始化
- 否则,如果实体是匿名联合或变体成员(9.5),则不执行初始化
- 否则,实体默认初始化(8.5)
[C++14: 8.5/12]:
如果没有为对象指定初始值设定项,则默认初始化该对象如果不执行初始化,则具有自动或动态存储持续时间的对象的值不确定
那么,标准是否保证替换对象的x
具有值1
?没有。没有。
在实践中,为什么可能不是呢?原因有很多。Class
的析构函数是非平凡的,因此,根据3.8,第一个对象的生存期在调用其析构函数后立即结束。
从技术上讲,编译器可以自由地将对象放置在该位置,只要在新放置生效时对象已被破坏即可。它没有理由在这里这样做,但没有什么禁止它;更重要的是,在析构函数调用和placementnew之间的一个简单的{ 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
现场演示
我不确定你还需要多少证据来证明标准不能保证这里的1
值。
- Qt5 远程对象 + 自定义类型,但不在 POD 中
- 对象已定义
- 有没有办法为静态对象成员定义一个符合开关标准的常量?
- C++ / G++ Maxmind geolite2++ 第三方共享对象未定义引用
- SFML-对象静态定义
- 通过"this"访问另一个对象的定义?
- 链接器声称该对象已定义
- 从链表访问对象(自定义实现)
- 在构造对象时定义成员函数
- C++中对象的定义
- 在对象的定义中创建对象
- 如何从 python 程序调用C++对象中定义的函数
- 无法加载共享对象:未定义的符号
- 其中是放置的cout、cin对象的定义
- C++分离了对象的定义和构造函数
- so中未定义的符号(在链接对象中定义)
- 需要继承类的对象在定义类之前是可见的
- 类的常量对象的定义
- 在映射对象上定义自定义迭代器:神秘的"incomplete type"错误
- 模板和静态对象(未定义的引用和必需的引用)