std :: optional如何永远不会“异常无价值”?

Dre*_*ann 14 c++ c++17 stdoptional std-variant

std::variant可以进入一种称为“ 异常无价值 ”的状态。

据我了解,这是导致移动分配抛出异常的常见原因。不能保证该变体的旧值不再存在,预期的新值也不是。

std::optional,但是没有这种状态。cppreference提出了大胆的主张:

如果引发异常,则* this ...的初始化状态不变,即,如果对象包含一个值,则它仍然包含一个值,反之亦然。

如何std::optional避免变得“异常无价值”而std::variant不能避免?

Bar*_*rry 17

optional<T> 具有以下两种状态之一:

  • 一种 T
  • 空的

variant仅当从一个状态转换到另一状态时,A 才能进入无值状态,否则转换会抛出-因为您需要以某种方式恢复原始对象,并且为此采取的各种策略需要额外的存储1,堆分配2或空状态3

但是对于optional,从过渡T到空只是破坏。因此,仅当T析构函数抛出时才抛出,而实际上谁在乎。从空状态过渡到T不是问题-如果抛出该异常,则很容易恢复原始对象:空状态为空。

具有挑战性的情况是:emplace()当我们已经有一个时T。我们一定需要销毁原始对象,所以如果Emplace结构抛出异常该怎么办?使用optional,我们有一个已知的方便的空状态可以回退-因此设计仅是要这样做。

variant从没有那么容易的状态恢复的问题。


1由于boost::variant2做。
2由于boost::variant做。
3我不确定执行此操作的变体实现,但是有人提出了一个设计建议,如果它保留an 并抛出异常,则variant<monostate, A, B>可以转换为monostate状态。AB


Ded*_*tor 8

std::optional 很简单:

  1. 它包含一个值并分配了一个新值:
    容易,只需委托给赋值运算符并让它处理它即可。即使在例外情况下,仍然会剩下一个值。

  2. 它包含一个值,并且该值已删除:
    很简单,dtor不能抛出。标准库通常假定用户定义的类型使用该库。

  3. 它不包含任何值,并且分配了一个值:
    面对构造异常时恢复为无值非常简单。

  4. 它不包含任何值,也没有分配任何值:
    琐碎的。

std::variant当存储的类型不变时,具有相同的轻松时间。
不幸的是,当分配了其他类型时,必须先销毁先前的值,然后再构造新的值,以取代它!

由于先前的值已经丢失,该怎么办?
将其标记为异常无价值,以使其具有稳定,有效但不理想的状态,并让异常传播。

可以使用额外的空间和时间来动态分配值,将旧值临时保存在某个地方,在分配值之前构造新值等,但是所有这些策略都很昂贵,只有第一种始终有效。


T.C*_*.C. 5

“异常无价值”是指您需要更改存储在变量中的类型的特定方案。这必然需要1)销毁旧值,然后2)在其位置创建新值。如果2)失败,则您无处可退(没有过多的开销,委员会无法接受)。

optional没有这个问题。如果对其包含的对象进行某些操作,则会引发异常,请这样。该对象仍然存在。这并不意味着对象的状态仍然有意义-不管抛出操作留下了什么。希望该操作至少具有基本的保证。

  • 从“可选”的角度来看,它仍然持有一个对象。该对象是否处于可用状态不是“可选”的问题。 (2认同)