std::optional 实现为 union vs char[]/aligned_storage

std::optional implemented as union vs char[]/aligned_storage

在阅读 GCC 对 std::optional 的实现时,我注意到了一些有趣的事情。我知道boost::optional的实现方式如下:

template <typename T>
class optional {
    // ...
private:
    bool has_value_;
    aligned_storage<T, /* ... */> storage_;
}

但是 libstdc++libc++(以及 Abseil)都实现了它们的 optional 类型如下:

template <typename T>
class optional {
    // ...
private:
    struct empty_byte {};
    union {
        empty_byte empty_;
        T value_;
    };
    bool has_value_;
}

在我看来,它们在功能上是相同的,但是使用一个比另一个有什么优势吗? (除了后者明显缺少 new placement 之外,这真的很不错。)

由于 post-C++17 缺陷修复,

std::optional 不能 实现为对齐存储。具体来说,如果 T 是平凡可复制的,则 std::optional<T> 必须是平凡可复制的。一个union{empty; T t};就可以满足这个要求

内部存储和放置-new/delete 用法不能。在 C++ 内存模型中,从 TriviallyCopyable 对象到尚未包含对象的存储进行字节复制不足以实际创建该对象。相比之下,编译器生成的已参与 union 在 TriviallyCopyable 类型上的副本将是微不足道的,并将用于创建目标对象。

所以std::optional必须这样实现。

They look to me as they are functionally identical, but are there any advantages of using one over the other? (Except for the obvious lack placement new in the latter which is really nice.)

它不仅仅是 "really nice" - 它对一个非常重要的功能至关重要,即:

constexpr std::optional<int> o(42);

在常量表达式中有几件事不能,包括newreinterpret_cast。如果使用 aligned_storage 实现 optional,则需要使用 new 创建对象并使用 reinterpret_cast 将其取回,这会阻止 optional constexpr 友好。

使用 union 实现,您不会遇到这个问题,因此您可以在 constexpr 编程中使用 optional(甚至在 fix for trivial copyability that is talking about, optional 已经被要求之前可用作 constexpr).