std::variant
は、「 例外では無価値 」という状態に入ることができます。
私が理解しているように、これの一般的な原因は、移動割り当てが例外をスローした場合です。バリアントの古い値が存在することは保証されておらず、意図された新しい値もありません。
std::optional
、しかし、そのような状態はありません。 cppreferenceは大胆な主張をしています。
例外がスローされた場合、* this ...の初期化状態は変更されません。つまり、オブジェクトに値が含まれていた場合でも、オブジェクトには値が含まれ、その逆になります。
std::optional
「例外によって無価値になる」のを回避できる一方で、std::variant
ではありません?
_optional<T>
_には次の2つの状態があります。
T
variant
は、遷移がスローされる場合、ある状態から別の状態に遷移するときにのみ値のない状態に入ることができます。元のオブジェクトを何らかの方法で回復する必要があり、そのためのさまざまな戦略には追加のストレージが必要なためです1、ヒープ割り当て2、または空の状態3。
しかし、optional
の場合、T
からemptyへの移行は単なる破壊です。そのため、T
のデストラクタがスローした場合にのみスローされ、実際にその時点で誰が気にしますか。空からT
への移行は問題ではありません。それがスローされた場合、元のオブジェクトを簡単に復元できます。空の状態は空です。
困難なケースは、T
がすでにある場合のemplace()
です。元のオブジェクトを破棄する必要があるので、エンプレース構造がスローされたらどうしますか? optional
を使用すると、フォールバックする既知の便利な空の状態が得られます。したがって、設計はそれを行うだけです。
variant
の問題は、簡単に回復できる状態にないためです。
1_boost::variant2
_ と同じように。
2_boost::variant
_ と同じように。
3 これを行うバリアントの実装がよくわかりませんが、monostate
が保持されている場合、_variant<monostate, A, B>
_がA
状態に遷移し、B
への遷移がスローされるという設計上の提案がありました。
std::optional
は簡単です:
これには値が含まれており、新しい値が割り当てられます。
簡単、代入演算子に委任して、それを処理させます。例外の場合でも、値が残っています。
これには値が含まれ、値は削除されます。
簡単に、dtorはスローしてはいけません。標準ライブラリは一般に、ユーザー定義型の場合を想定しています。
値は含まれず、1つが割り当てられます。
構築の例外が発生しても値をゼロに戻すのは簡単です。
値は含まれておらず、値は割り当てられていません。
平凡。
std::variant
には、格納されているタイプが変更されない場合と同じ簡単な時間があります。
残念ながら、別のタイプが割り当てられている場合は、以前の値を破棄して、代わりに新しいタイプの値を作成する必要があります。
以前の価値はすでに失われているので、あなたは何ができますか?
それを例外による無価値としてマークし、安定した、有効であるが望ましくない状態にして、例外を伝播させます。
余分なスペースと時間を使用して値を動的に割り当て、古い値を一時的にどこかに保存し、割り当てる前に新しい値を作成するなどができますが、これらの方法はすべてコストがかかり、最初の方法のみが常に機能します。
「例外による無価値」とは、バリアントに格納されているタイプを変更する必要がある特定のシナリオを指します。それには、必ず1)古い値を破棄してから、2)その場所に新しい値を作成する必要があります。 2)失敗した場合、戻る方法はありません(委員会に受け入れられない過度のオーバーヘッドがない場合)。
optional
にはこの問題はありません。含まれているオブジェクトに対する何らかの操作が例外をスローする場合は、例外です。オブジェクトはまだそこにあります。それはオブジェクトの状態がまだ意味があることを意味しません-それは投げる操作がそれを残しているものです。うまくいけば、その操作は少なくとも基本的な保証を持っています。