是否可以使用联合成员作为对象存储?

Is it possible to use a union member as object storage?

我想知道我们是否可以使用一个union成员作为显式初始化和析构对象的存储,例如在下面的代码中:

struct X
{
    X() { std::cout << "C"; }
    ~X() { std::cout << "D"; }
};

struct X_owner
{
    union
    {
        X x;  // storage for owned object
    };

    X_owner()
    {
        new (&x) X{};
    }

    ~X_owner()
    {
        (&x)->~X();
    }
};

int main()
{
    X_owner xo;
}

打印输出仅符合预期 CD。现场演示:https://godbolt.org/z/M1Gov4o4d

这个问题是由一项编程作业引发的,在该作业中,学生应该明确定义对象的存储。预期的解决方案是 unsigned charstd::bytestd::aligned_storege 类型的适当对齐和大小的缓冲区。然而,他们中很少有人在他们的解决方案中使用这种方式 union 。我不确定这是否正确,尽管我找不到任何错误。

注意:为了简单起见,我在这个例子中不关心copy/move语义。

显示的代码是正确的。为了功能完整,应该正式定义复制构造函数和赋值运算符。如果 union 要容纳另一个对象,事情会很快变得复杂。为了保持有效,包装器对象将需要明确跟踪联合中构建的底层对象,并自行处理这些问题。

但是,如果事情朝那个方向发展,你可以让 std::variant 处理它。

确实有效

[class.base.init]

9 In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

  • [...]
  • otherwise, if the entity is an anonymous union or a variant member ([class.union.anon]), no initialization is performed;
  • [...]

所以 x 没有被初始化,所以在它的生命周期内没有被初始化,直到一个人明确地对那块存储做一些事情来在那里创建一个对象。新的放置甚至没有替换该存储中的对象,直到 new 表达式才出现。所以我们甚至没有深入研究可能需要 std::launder 的地方。

似乎是一种可以接受的方法。