c++ c++17 stdoptional std-variant

c++ - ¿Cómo es std:: opcional nunca "sin valor por excepción"?



c++17 stdoptional (3)

std::variant puede entrar en un estado llamado " sin valor por excepción ".

Según tengo entendido, la causa común de esto es si una asignación de movimiento arroja una excepción. Ya no se garantiza que el valor anterior de la variante esté presente, ni tampoco el nuevo valor previsto.

std::optional , sin embargo, no tiene ese estado. cppreference hace la afirmación en negrita:

Si se produce una excepción, el estado de inicialización de * this ... no cambia, es decir, si el objeto contenía un valor, todavía contiene un valor, y viceversa.

¿Cómo es std::optional capaz de evitar ser "sin valor por excepción", mientras que std::variant no lo es?


"sin valor por excepción" se refiere a un escenario específico donde necesita cambiar el tipo almacenado en la variante. Eso necesariamente requiere 1) destruir el valor anterior y luego 2) crear el nuevo en su lugar. Si 2) falla, no tiene forma de regresar (sin gastos indebidos inaceptables para el comité).

optional no tiene este problema. Si alguna operación en el objeto que contiene produce una excepción, que así sea. El objeto sigue ahí. Eso no significa que el estado del objeto siga siendo significativo, es lo que sea que la operación de lanzamiento lo deje. Esperemos que esa operación tenga al menos la garantía básica.


optional<T> tiene uno de dos estados:

  • una T
  • vacío

Una variant solo puede ingresar al estado sin valor cuando se realiza la transición de un estado a otro si la transición arroja, porque de alguna manera necesita recuperar el objeto original y las diversas estrategias para hacerlo requieren almacenamiento adicional 1 , asignación de almacenamiento dinámico 2 o un estado vacío 3)

Pero para optional , la transición de T a vacío es solo una destrucción. Entonces eso solo arroja si el destructor de T lanza, y realmente a quién le importa en ese punto. Y la transición de vacío a T no es un problema; si eso arroja, es fácil recuperar el objeto original: el estado vacío está vacío.

El caso desafiante es: emplace() cuando ya teníamos una T Necesitamos necesariamente haber destruido el objeto original, entonces, ¿qué hacemos si la construcción del emplazamiento arroja? Con optional , tenemos un estado vacío conveniente y conocido al que recurrir, por lo que el diseño es solo para hacer eso.

problemas de la variant de no tener ese estado fácil para recuperarse.

1 Como boost::variant2 hace.
2 Como boost::variant hace.
3 No estoy seguro de una implementación de variante que haga esto, pero hubo una sugerencia de diseño de que la variant<monostate, A, B> podría pasar al estado de monostate si tuviera una A y la transición a B produjera.


std::optional tiene fácil:

  1. Contiene un valor y se le asigna un nuevo valor:
    Fácil, simplemente delegue en el operador de asignación y deje que se ocupe de ello. Incluso en el caso de una excepción, todavía quedará un valor.

  2. Contiene un valor y el valor se elimina:
    Fácil, el dtor no debe tirar. La biblioteca estándar generalmente supone que para los tipos definidos por el usuario.

  3. No contiene ningún valor y se le asigna uno:
    Volver a ningún valor frente a una excepción en la construcción es bastante simple.

  4. No contiene ningún valor y no se le asigna ningún valor:
    Trivial.

std::variant tiene el mismo tiempo fácil cuando el tipo almacenado no cambia.
Desafortunadamente, cuando se le asigna un tipo diferente, debe dar lugar destruyendo el valor anterior y luego construir el nuevo valor.

Como el valor anterior ya está perdido, ¿qué puede hacer?
Márquelo como sin valor por excepción para tener un estado estable, válido aunque indeseable, y deje que la excepción se propague.

Se podría usar espacio y tiempo extra para asignar los valores dinámicamente, guardar el valor antiguo en algún lugar temporalmente, construir el nuevo valor antes de asignarlo o similar, pero todas esas estrategias son costosas y solo la primera siempre funciona.