c++ - 一个对象被销毁后,标量类型的子对象会发生什么?

标签 c++ destructor language-lawyer object-lifetime explicit-destructor-call

考虑这段代码(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 段:

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.

第 3 段:

To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

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

3.8 第 1 段:

The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

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.

顺便说一句,3.7.3 p1:

The storage for these [automatic storage duration] entities lasts until the block in which they are created exits.

和 3.7.5:

The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (1.8).

因此,在此示例中,不用担心编译器会在 exit 之前“释放”存储。

3.8p2中的一个非规范性注释中提到“12.6.2描述了基类和成员子对象的生命周期”,但是那里的语言只谈初始化和析构函数,而不谈“存储”或“生命周期”,所以我得出结论该部分不影响普通类型子对象的“生命周期”定义。

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

3.8 第 5 段和第 6 段说,在任何对象的生命周期之前或之后对“分配的存储”的指针和引用可以以有限的方式使用,并列出了很多你不能用它们做的事情。左值到右值的转换,如表达式 ref == 42 所要求的,是其中之一,但如果 int 的生命周期尚未到来,这不是问题结束了。

所以我认为 renew 为 false,程序是良构的并且 assert 成功了!

renew为真时,存储被程序“重用”,所以原来的int的生​​命周期结束了,另一个int的生命周期结束了 开始。但随后我们进入 3.8 第 7 段:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

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

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

12.6.2 第 8 段:

After the call to a constructor for class X has completed, if a member of X is neither initialized nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.

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

关于c++ - 一个对象被销毁后,标量类型的子对象会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11637611/

相关文章:

scala - 如何在 Scala 中编写类析构函数?

c++ - 跨 TU 的类模板重载

c++ - 分段如何提高埃拉托色尼筛法的运行时间?

c++必须删除引用吗?

c++ - C++中的析构函数执行顺序

c++ - 为什么std::vector和std::valarray初始化构造函数不同?

c++ - 成员到指针变量仅在带有-std = c++ 17的Clang中被接受为函数模板参数

c++ - 基于嵌套的内部参数专门化模板

c++ - 关于根据 move 赋值和 move 构造函数实现 std::swap

c++ - 在 OpenCV 中将 HSV 转换为 RGB 失败