对象被销毁后,标量类型的子对象会发生什么

After an object is destroyed, what happens to subobjects of scalar type?

本文关键字:对象 什么 标量 类型      更新时间:2023-10-16

考虑以下代码(对于renewcleanse的不同值):

struct T {
int mem;
T() { }
~T() { mem = 42; }
};
// identity functions, 
// but breaks any connexion between input and output
int &cleanse_ref(int &r) {
int *volatile pv = &r; // could also use cin/cout here
return *pv;
}
void foo () {
T t;
int &ref = t.mem;
int &ref2 = cleanse ? cleanse_ref(ref) : ref;
t.~T();
if (renew)
new (&t) T;
assert(ref2 == 42);
exit(0);
}

assert是否保证通过?

我知道这种风格是不推荐像"这不是一个好的做法"这样的观点在这里不感兴趣。

我想要一个显示标准引号的完整逻辑证明的答案。编译器作者的观点可能也很有趣。

编辑:现在两个问题合一!请参阅renew参数(对于renew == 0,这是原始问题)。

编辑2:我想我的问题是:什么是成员对象?

编辑3:现在使用另一个cleanse参数!

我最初有这两个引号,但现在我认为它们实际上只是指定了像int &ref = t.mem;这样的事情必须在t的生命周期内发生。在你的例子中确实如此。

12.7第1段:

对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类会导致未定义的行为。

和第3段:

要形成指向对象obj的直接非静态成员的指针(或访问其值),obj的构建应已开始,其销毁应尚未完成,否则指针值的计算(或访问成员值)将导致未定义的行为。

这里有一个类型为T的完整对象和一个类型int的成员子对象。

3.8第1段:

类型为T的对象的生存期从以下时间开始:

对于类型T,获得了具有适当排列和大小的
  • 存储,并且
  • 如果对象进行了非平凡的初始化,那么它的初始化就完成了

类型为T的对象的生存期在以下情况下结束:

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

顺便说一句,3.7.3 p1:

这些[自动存储持续时间]实体的存储将持续到创建它们的块退出为止。

和3.7.5:

成员子对象、基类子对象和数组元素的存储时间是其完整对象的存储时间(1.8)

所以不用担心编译器"释放";在本例中为CCD_ 17之前的存储器。

3.8p2中的非规范性注释提到;12.6.2描述了基本子对象和成员子对象的寿命;但那里的语言只谈论初始化和析构函数,而不是";存储";或";寿命";,因此我得出结论,该部分不影响";寿命;对于琐碎类型的子对象。

如果我正确地解释了这一切,当renew为false时,完整类对象的生存期将在显式析构函数调用结束时结束,但int子对象的生命期将持续到程序结束。

3.8第5和第6段指出;分配的存储器";在任何对象的生命周期之前或之后,都可以以有限的方式使用,并列出许多您可能无法使用它们的事情。像表达式ref == 42所要求的那样,从左值到右值的转换就是其中之一,但如果int的生命周期尚未结束,这就不是问题。

所以我认为renew为假,程序形成良好,assert成功了!

renew为真的情况下;"重复使用";则原int的寿命结束,另一个int的寿命开始。但接下来我们进入3.8第7段:

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

  • 新对象的存储正好覆盖原始对象占用的存储位置,并且
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),并且
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型是const限定或引用类型的非静态数据成员,并且
  • 原始对象是类型为T的最派生对象(1.8)并且新对象是类型T的最派生的对象(即它们不是基类子对象)

这里的第一个要点是最棘手的。对于像T这样的标准布局类,同一个成员肯定总是在同一个存储中。当类型不是标准布局时,我不确定这在技术上是否是必需的。

尽管ref是否仍然可以使用,但在这个例子中还有另一个问题。

12.6.2第8段:

对类X的构造函数的调用完成后,如果在执行构造函数主体的复合语句期间,X的成员既没有初始化也没有给定值,则该成员的值不确定。

如果它将t.mem设置为零或0xDEADBEEF,则意味着实现是兼容的(有时调试模式会在调用构造函数之前实际执行这些操作)。

您没有销毁内存,只是手动调用了析构函数(在这种情况下,它与调用普通方法没有什么不同)。t变量的内存(堆栈部分)未"释放"。因此,这个断言将始终与当前代码一起传递。

相关文章: