Как std::option никогда не "бесполезен по исключению"?
std::variant
может войти в состояние, называемоебесценным по исключению".
Как я понимаю, общей причиной этого является то, что назначение перемещения вызывает исключение. Не гарантируется, что старое значение варианта больше не будет присутствовать, как и предполагаемое новое значение.
std::optional
, однако, не имеет такого состояния. cppreference делает смелое утверждение:
Если выдается исключение, состояние инициализации * this... не изменяется, т.е. если объект содержит значение, он все еще содержит значение, и наоборот.
Как std::optional
может избежать того, чтобы стать "бесполезным по исключению", а std::variant
- нет?
Ответы
Ответ 1
optional<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>
может перейти в состояние monostate
, если он удерживает A
и переход к методу B
.
Ответ 2
std::optional
легко:
Он содержит значение, и ему присваивается новое значение:
Просто, просто делегируйте оператору присваивания и дайте ему разобраться с этим. Даже в случае исключения все равно останется значение.
Он содержит значение, и значение удаляется:
Легко, дтор не должен бросать. Стандартная библиотека обычно предполагает это для пользовательских типов.
Он не содержит значения и назначен одному:
Возврат к нулевому значению перед лицом исключения при построении достаточно прост.
Он не содержит значения, и значение не назначено:
Trivial.
std::variant
имеет такое же легкое время, когда сохраненный тип не изменяется.
К сожалению, когда назначается другой тип, он должен освободить место для него, уничтожив предыдущее значение, а затем построив новое значение, можно выбросить!
Поскольку предыдущее значение уже потеряно, что вы можете сделать?
Отметьте его как бесполезный по исключению, чтобы иметь стабильное, допустимое, хотя и нежелательное состояние, и разрешите распространению исключения.
Можно использовать дополнительное пространство и время для динамического выделения значений, временного сохранения старого значения где-либо, создания нового значения перед присвоением и т.п., Но все эти стратегии являются дорогостоящими, и всегда работает только первая.
Ответ 3
"бесценный по исключению" относится к конкретному сценарию, в котором необходимо изменить тип, сохраненный в варианте. Это обязательно требует 1) уничтожения старого значения и затем 2) создания нового на его месте. Если 2) не удается, у вас нет возможности вернуться (без чрезмерных накладных расходов, неприемлемых для комитета).
optional
не имеет этой проблемы. Если какая-то операция над объектом, который он содержит, выдает исключение, пусть будет так. Объект все еще там. Это не означает, что состояние объекта все еще имеет смысл - независимо от того, в какой операции броска оно оставлено. Надеемся, что эта операция имеет хотя бы базовую гарантию.