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);
在常量表达式中不能做几件事,包括new
和reinterpret_cast
。如果您使用aligned_storage
实现optional
,则需要使用new
创建对象并使用reinterpret_cast
将其取出,这将阻止optional
对constexpr
友好。
使用union
实现,您就没有这个问题,因此您可以在constexpr
编程中使用optional
(甚至在Nicol 所说的fix for trivial copyability 之前,optional
已经需要作为constexpr
使用)。
【讨论】:
【参考方案2】:std::optional
不能实现为对齐存储,由于 C++17 后的缺陷修复。具体来说,如果T
可简单复制,则std::optional<T>
必须可简单复制。 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>
std::optional - 用 或 std::nullopt 构造空?
`std::optional` 比 `std::shared_ptr` 和 `std::unique_ptr` 有啥优势?