Как std::option никогда не "бесполезен по исключению"?

std::variant может войти в состояние, называемоебесценным по исключению".

Как я понимаю, общей причиной этого является то, что назначение перемещения вызывает исключение. Не гарантируется, что старое значение варианта больше не будет присутствовать, как и предполагаемое новое значение.

std::optional, однако, не имеет такого состояния. cppreference делает смелое утверждение:

Если выдается исключение, состояние инициализации * this... не изменяется, т.е. если объект содержит значение, он все еще содержит значение, и наоборот.

Как std::optional может избежать того, чтобы стать "бесполезным по исключению", а std::variant - нет?

Ответы

Ответ 1

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> может перейти в состояние monostate, если он удерживает A и переход к методу B.

Ответ 2

std::optional легко:

  1. Он содержит значение, и ему присваивается новое значение:
    Просто, просто делегируйте оператору присваивания и дайте ему разобраться с этим. Даже в случае исключения все равно останется значение.

  2. Он содержит значение, и значение удаляется:
    Легко, дтор не должен бросать. Стандартная библиотека обычно предполагает это для пользовательских типов.

  3. Он не содержит значения и назначен одному:
    Возврат к нулевому значению перед лицом исключения при построении достаточно прост.

  4. Он не содержит значения, и значение не назначено:
    Trivial.

std::variant имеет такое же легкое время, когда сохраненный тип не изменяется.
К сожалению, когда назначается другой тип, он должен освободить место для него, уничтожив предыдущее значение, а затем построив новое значение, можно выбросить!

Поскольку предыдущее значение уже потеряно, что вы можете сделать?
Отметьте его как бесполезный по исключению, чтобы иметь стабильное, допустимое, хотя и нежелательное состояние, и разрешите распространению исключения.

Можно использовать дополнительное пространство и время для динамического выделения значений, временного сохранения старого значения где-либо, создания нового значения перед присвоением и т.п., Но все эти стратегии являются дорогостоящими, и всегда работает только первая.

Ответ 3

"бесценный по исключению" относится к конкретному сценарию, в котором необходимо изменить тип, сохраненный в варианте. Это обязательно требует 1) уничтожения старого значения и затем 2) создания нового на его месте. Если 2) не удается, у вас нет возможности вернуться (без чрезмерных накладных расходов, неприемлемых для комитета).

optional не имеет этой проблемы. Если какая-то операция над объектом, который он содержит, выдает исключение, пусть будет так. Объект все еще там. Это не означает, что состояние объекта все еще имеет смысл - независимо от того, в какой операции броска оно оставлено. Надеемся, что эта операция имеет хотя бы базовую гарантию.