PAYATHON 2026

C++ 隐式对象创建是否适用于多层嵌套的子对象?

数据阿明

结论

隐式对象创建规则适用于多层嵌套的 implicit-lifetime 子对象,包括数组元素。外层对象创建后,其成员不会因此被无条件递归初始化。

标准指定的隐式创建操作可以在其作用的存储区域内,同时创建使程序行为有定义所需的一组 implicit-lifetime 对象,并开始它们的生存期。这组对象可以包含数组、数组元素及更深层的子对象。标准草案:[intro.object]

为什么不限于最外层对象?

[intro.object] 的规则针对指定存储区域中的“零个或多个对象”,范围不限于完整对象或直接子对象。如果存在多组能使程序行为有定义的对象,具体创建哪组是未指定的;如果不存在这样的对象集合,程序行为未定义。

可以用“递归”描述创建后的对象层级,但不必将其理解为逐层执行的构造过程。标准同节的 std::malloc 示例明确包括类对象及其成员子对象的隐式创建。标准草案:[intro.object]

原例中的数组和元素如何区分?

数组类型本身属于 implicit-lifetime type,其元素类型不一定属于这一类别,因此需要分别判断数组和元素。标准草案:[basic.types.general]

  • 如果 T 是 implicit-lifetime type,隐式创建可以涵盖 T 元素及其符合条件的嵌套子对象。
  • 如果 T 不是 implicit-lifetime type,这种操作不会开始 T 元素的生存期,即使外层数组的生存期已经开始。标准草案:[intro.object]

“非平凡类型”与“非 implicit-lifetime type”也不能直接画等号。类是否属于 implicit-lifetime class,要按聚合、构造函数和析构函数等具体条件判断。标准草案:[class.prop]

多层嵌套的代码示例

下面沿用问题中的字节数组存储方式,展示数组元素及其嵌套成员的使用:

#include <cstddef>
#include <new>

struct Inner {
    int value;
};

struct Outer {
    Inner inner;
};

void example() {
    alignas(Outer) std::byte storage[2 * sizeof(Outer)];

    auto* bytes = new (static_cast<void*>(storage))
        std::byte[2 * sizeof(Outer)];

    auto* p = std::launder(reinterpret_cast<Outer*>(bytes));

    p[0].inner.value = 42;
    p[1].inner.value = 7;
}

这里的 Outer、Inner 和 int 都是 implicit-lifetime types。字节数组开始生存期的操作允许在其存储中隐式创建所需对象,其中可以包括 Outer[2]、两个 Outer 元素及其嵌套成员。标准草案:[intro.object]

代码给 value 赋值,没有读取尚未赋值的整数。对象的生存期开始后,它不一定已经获得适合读取的初始值。

std::launder 是否负责创建对象?

std::launder 用于取得指向满足其前提条件的对象的指针;它本身不执行隐式对象创建,也不调用构造函数。原例中的隐式创建来自相应的存储操作。标准草案:[ptr.launder]

当前公开工作草案允许 std::launder 的目标为生存期内的对象,或所属数组仍在生存期内的数组元素子对象,同时要求满足存储可达性条件。因此,取得元素指针不代表元素本身已经构造完成。具体使用时应核对目标标准版本的措辞。标准草案:[ptr.launder]

对于尚未开始生存期的非 implicit-lifetime 元素,应在使用前通过 placement new 或适用的 std::construct_at 显式构造,并安排相应的析构。外层数组存在,不足以允许正常访问这些元素的成员。标准草案:[basic.life]

std::bit_cast 为什么单独提到嵌套对象?

std::bit_cast 的那句话指定了该操作会隐式创建结果内部的嵌套对象,并引用 [intro.object] 的通用规则。其他操作是否也能创建子对象,要看它们各自是否被标准指定为隐式创建操作。标准草案:[bit.cast]

判断具体代码时,依次检查触发操作、存储范围及对齐、各层类型是否符合条件,以及读取前是否已有有效值。一次指针转换或 std::launder 调用不足以表明所有对象均已就绪。

备注:内容仅供参考。