std::variant
可以进入状态“ valueless by exception”。
据我了解,这种情况的常见原因是移动分配抛出异常。不能保证该变体的旧值不再存在,预期的新值也不是。
std::optional
没有这种状态。 cppreference做出了大胆的声明:
如果引发异常,则* this ...的初始化状态不变,即,如果对象包含一个值,则它仍然包含一个值,反之亦然。
std::optional
如何能够避免变得“异常毫无价值”,而std::variant
却无法避免?
答案 0 :(得分:17)
optional<T>
具有以下两种状态之一:
T
variant
仅在从一个状态转换到另一状态时,如果转换将引发,则只能进入无值状态-因为您需要以某种方式恢复原始对象,并且这样做的各种策略都需要额外的存储空间 1 ,堆分配 2 或空状态 3 。
但是对于optional
来说,从T
过渡到空状态只是一种破坏。因此,只有在T
的析构函数抛出时才会抛出,而实际上谁在乎。从空过渡到T
并不是问题-如果抛出该异常,则很容易恢复原始对象:空状态为空。
具有挑战性的情况是:emplace()
,当我们已经有T
时。我们一定需要销毁原始对象,所以如果Emplace结构抛出异常该怎么办?使用optional
,我们有一个已知的方便的空状态可以回退-因此,设计就是为了做到这一点。
variant
的问题在于无法恢复到如此简单的状态。
1 就像boost::variant2
一样。
2 就像boost::variant
一样。
3 我不确定执行此操作的变体实现,但是有一个设计建议,如果variant<monostate, A, B>
保持{{1 }},然后过渡到monostate
。
答案 1 :(得分:8)
std::optional
很简单:
它包含一个值并分配了一个新值:
简单,只需委托给赋值运算符并让它处理它即可。即使在例外情况下,仍然会剩下一个值。
它包含一个值,并且该值已删除:
简单,dtor一定不要扔。标准库通常假定用户定义的类型是这样。
它不包含任何值,并且分配了一个值:
面对构造异常时恢复为零值非常简单。
它不包含任何值,也没有分配任何值:
小事。
std::variant
在存储类型不变的情况下具有相同的便捷时间。
不幸的是,如果分配了其他类型,则必须先销毁先前的值,然后再构造新的值,以取代它!
由于先前的值已经丢失,该怎么办?
将其标记为异常无值,以使其具有稳定,有效但不理想的状态,并让异常传播。
人们可以使用额外的空间和时间来动态分配值,将旧值临时保存在某个地方,在分配值之前构造新值,等等,但是所有这些策略都很昂贵,只有第一种始终有效。
答案 2 :(得分:5)
“异常无值”是指您需要更改存储在变量中的类型的特定方案。这必然需要1)销毁旧值,然后2)在其位置创建新值。如果2)失败,则您无处可退(没有过多的开销,委员会无法接受)。
optional
没有这个问题。如果对其包含的对象进行某些操作,则会引发异常,请这样。该对象仍然存在。但这并不意味着对象的状态仍然有意义-不管将其扔到哪里都可以。希望该操作至少具有基本的保证。