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

Posted

技术标签:

【中文标题】std::optional 实现为 union vs char[]/aligned_storage【英文标题】:std::optional implemented as union vs char[]/aligned_storage 【发布时间】:2019-02-19 15:18:28 【问题描述】:

在阅读 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 ,这真的很好。)

【问题讨论】:

【参考方案1】:

在我看来,它们在功能上是相同的,但是使用其中一个有什么优势吗? (除了后者明显缺少新的位置,这真的很好。)

这不仅仅是“非常好” - 它对于一个非常重要的功能至关重要,即:

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

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

使用union 实现,您就没有这个问题,因此您可以在constexpr 编程中使用optional(甚至在Nicol 所说的fix for trivial copyability 之前,optional 已经需要作为constexpr 使用)。

【讨论】:

【参考方案2】:

std::optional不能实现为对齐存储,由于 C++17 后的缺陷修复。具体来说,如果T 可简单复制,则std::optional&lt;T&gt; 必须可简单复制。 unionempty; T t; 将满足此要求

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

所以std::optional必须以这种方式实现。

【讨论】:

为什么将 TriviallyCopyable 对象的字节副本复制到未初始化的存储不会创建该对象?在我看来,大多数原始类型都应该这样做,你能举个例子说明为什么不这样做吗? @RonMordechai:因为 [intro.object]/1 这么说。或者更确切地说,它列出了创建对象的句法结构,而“复制内存”不在其中。因此,它不能创建对象。内存中的值不会自行产生对象。这是“有效”的 UB 的常见情况。 为什么需要空字段?它可以简单地与union T t; ; 一起工作吗? @random:因为T 可能不是默认可构造的。或可分配。当optional 没有T 时,必须以没有T 的方式完成。如果你有一个包含单个元素的联合,它总是存储该类型的元素。 @NicolBolas 你的意思是如果联合只有一个成员,它将自动构建?我刚刚在 Visual Studio 中进行了实验,并且没有调用构造函数。所以看起来 union 默认不存储任何对象。

以上是关于std::optional 实现为 union vs char[]/aligned_storage的主要内容,如果未能解决你的问题,请参考以下文章

将一个 std::optional 转换为另一个 std::optional

C++ 标准是不是允许在没有开销的情况下实现 std::optional<double>

如何在 C++ 中使用 std::optional ?

std::optional - 用 或 std::nullopt 构造空?

什么时候适合使用 std::optional

`std::optional` 比 `std::shared_ptr` 和 `std::unique_ptr` 有啥优势?