结束一个子对象的生命周期会对包含它的对象以及其他子对象产生怎样的影响?

编程语言 2026-07-10

我想了解关于对象生命周期结束和存储复用的一些细节。
我已经问过 this question 关于复用留下的占用范围的影响,并且得到了一个关于简单对象的恰当回答(简而言之:无论复用留下多大的占用范围,它都会结束基本类型或指针对象的生命周期)。
但它自然也会扩展到复合类型的对象,例如:

struct A
{
   double d;   
   int i;
   double c1;
   double d1;
   double d2;
};

(可以用一个更简单的结构体来说明这个问题,但我这里重用了另一道问题中提到的那个,详见下文。)
Another question 链接讨论的情形是:类型为A 的对象直接成为placement new的目标,从而结束一个子对象的生存期(问题中的 c1)。由于 A 类型的存储并不提供存储空间,因此被视为完全被复用,结束整个对象的生命周期,并且也结束所有子对象的生命周期。
(为求完整性 this question 讨论了子对象被显式销毁的类似情形。)

剩下的问题是:如果存储是由一个字节数组提供,会怎样?

#include <cstddef>
#include <cstdint>
#include <memory>

struct A {
    double d;
    int i;
    double c1;
    double d1;
    double d2;
};

int main() {
    alignas(A) std::byte storage[sizeof(A)];
    std::construct_at(reinterpret_cast<A*>(storage), 1., 2, 3., 4., 5.);
    // There is a live A object in storage
    std::construct_at(
        reinterpret_cast<std::uint16_t*>(storage + offsetof(A, c1)),
        std::uint16_t{0});
    // c1 storage is reused, thus ending c1 lifetime: does it ends the A
    // object lifetime, does it ends other subobjects lifetimes?
}

LIVE

当然,这种模式我也想不到实际用途,但我觉得它有助于清楚理解子对象与对象之间就寿命方面的关系。

因此问题是,如注释中所述,结束 c1 的生命周期是否也结束类型为 A 的封装对象的生命周期?它是否也结束其他子对象的生命周期?

到目前为止,我在标准中只发现以下情形的支持:对象生命周期的开始在某些情形下才会使其子对象的生命周期开始(构造函数被调用,数组的表达式 new[] 等)。对象生命周期的结束也在某些情形下只结束其子对象的生命周期(调用析构函数,当封装对象是数组时,delete[])。
因此我的预期是,封装对象的生命周期在该链接所述的规则下结束,但它的子对象(除了 c1 之外)仍然处于生命周期中,而 storage + offsetof(A, c1) 不再是存放活着的 double 的位置(因此 *std::launder(reinterpret_cast<std::uint16_t*>(storage + offsetof(A, c1))) 将是未定义行为UB)。

另一方面,我对 *std::launder(reinterpret_cast<double*>(storage + offsetof(A, d1))) 的命运不确定(例如)。

如果能有标准表述来支持或否定我的主张并澄清情形,我将不胜感激。

解决方案

为方便命名,我将对你的代码做一点小修改:

alignas(A) std::byte Storage[sizeof(A)];
auto* pA = std::construct_at(reinterpret_cast<A*>(Storage), 1., 2, 3., 4., 5.);
// There is a live A object in Storage
auto* pNotC1 = std::construct_at(
    reinterpret_cast<std::uint16_t*>(Storage + offsetof(A, c1)),
    std::uint16_t{0});

通过再添加两个指针。这些指针不会改变答案,只是方便我更容易地给东西命名。我还把它叫成 Storage,用大写字母,以免和标准术语混淆。

[basic.life]/2.2-2.5:

The lifetime of an object o of type T ends when:

[...]

the storage which the object occupies is released, or is reused by an object that is not nested within o

[intro.object]/4:

4 An object a is nested within another object b if (4.1) a is a subobject of b, or (4.2) b provides storage for a, or (4.3) there exists an object c where a is nested within c, and c is nested within b.

[intro.object]/3:

If a complete object is created ([expr.new]) in storage associated with another object e of type “array of N unsigned char” or of type “array of N std​::​byte” ([cstddef.syn]), that array provides storage for the created object if (3.1) the lifetime of e has begun and not ended, and (3.2) the storage for the new object fits entirely within e, and (3.3) there is no array object that satisfies these constraints nested within e.

在我们构造 *pA 之后的时刻:

Storage 数组为 *pA 对象提供存储。*pA 对象嵌套在 Storage 内。

pA->c1 也嵌套在 *pA 内,因此也嵌套在 Storage 内。

然后我们构造 *pNotC1。在这里,*pAStorage 的生存期正面临风险,因为我们正在使用 *pAStorage 所占据的存储来构造 *pNotC1

*pNotC1 是嵌套在 *pA 还是 Storage 的内部?

*pNotC1 不是 *pA 的子对象;子对象是 pA->c1*pA 并不为 *pNotC1 提供存储,因为只有数组才能提供存储。因此 *pA 的生存期结束。

我们在 Storage 内访问 *pNotC1*pNotC1Storage 提供存储,因此嵌套在 Storage 之内。因此 Storage 仍然存在。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章