在阅读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_;
}
他们看起来因为它们在功能上是相同的,但使用一个优于另一个有什么优势吗? (除了明显缺乏后者的新位置,这真的很好。)
他们看起来因为它们在功能上是相同的,但使用一个优于另一个有什么优势吗? (除了明显缺乏后者的新位置,这真的很好。)
这不仅仅是“非常好” - 它对于一个非常重要的功能至关重要,即:
constexpr std::optional<int> o(42);
在常量表达式中有几件事你不能做,其中包括new
和reinterpret_cast
。如果你用optional
实现aligned_storage
,你需要使用new
创建对象和reinterpret_cast
以使其恢复,这将阻止optional
被constexpr
友好。
使用union
实现,你没有这个问题,所以你可以在optional
编程中使用constexpr
(甚至在fix for trivial copyability讨论的Nicol之前,optional
已经被要求可用作constexpr
)。
由于后C ++ 17缺陷修复,std::optional
无法实现为对齐存储。具体来说,如果std::optional<T>
是可以轻易复制的,那么T
必须是可以轻易复制的。 union{empty; T t};
将满足此要求
内部存储和放置 - new
/ delete
使用不能。在C ++内存模型中,从TriviallyCopyable对象执行字节复制到尚未包含对象的存储是不够的,无法实际创建该对象。相比之下,编译器生成的关于TriviallyCopyable类型的引用union
的副本将是微不足道的,并且将用于创建目标对象。
所以std::optional
必须以这种方式实现。