versiones dev compiler c++ c++11

c++ - dev - ¿Por qué noexcept no se aplica en tiempo de compilación?



c++17 (4)

Como entiendo las cosas (es cierto que algo confuso), se descubrió que la idea completa de especificaciones de lanzamiento era una pesadilla cuando llegó el momento de tratar de usarla de manera útil.

¡Se debe considerar que las funciones de llamada que no especifican lo que lanzan o no lanzan pueden lanzar algo! Entonces, el compilador, si fuera a pedirle que no lanzara ni llamara nada que pudiera lanzar algo fuera de la especificación que se le proporcionó, realmente hace cumplir tal cosa, su código podría llamar casi nada, ninguna biblioteca en existencia sería de alguna utilidad. para usted o cualquier otra persona que trate de hacer uso de las especificaciones de lanzamiento.

Y dado que es imposible que un compilador diga la diferencia entre "Esta función puede lanzar una X, pero la persona que llama puede llamarla de tal manera que nunca lance nada", uno siempre quedará paralizado. este lenguaje "característica".

Así que ... creo que lo único que posiblemente resultó útil fue la idea de decir no-show, lo que indica que es seguro llamar desde dtors y moverse e intercambiar, etc., pero que estás haciendo una notación de que - Al igual que const - se trata más de otorgar a sus usuarios un contrato de API en lugar de responsabilizar al compilador de saber si viola su contrato o no (como en la mayoría de los casos de C / C ++) se supone que la inteligencia está en la parte del programador , no el compilador de niñeras).

Como sabrán, C ++ 11 no tiene ninguna palabra clave. Ahora la parte fea de esto es esto:

Tenga en cuenta que una especificación de excepción en una función no es una verificación en tiempo de compilación; es simplemente un método para que un programador informe al compilador si una función debe lanzar excepciones o no.

http://en.cppreference.com/w/cpp/language/noexcept_spec

Entonces, ¿esto es un fallo de diseño en la parte del comité o simplemente lo dejaron como un ejercicio para los escritores de compilación :) en el sentido de que los compiladores decentes lo aplicarán, los malos todavía pueden ser compatibles?

Por cierto, si pregunta por qué no hay una tercera opción (también conocida como no se puede hacer) la razón es que puedo pensar fácilmente en una forma (lenta) de comprobar si la función puede lanzar o no. El problema está fuera de curso si limita la entrada a 5 y 7 (también prometo que el archivo no contendrá nada al lado de 5 y 7) y solo se lanza cuando le da 33, pero ese no es un problema realista, IMHO.


El comité consideró bastante claramente la posibilidad de que el código que (intentó) lanzar una excepción no permitida por una especificación de excepción se considerara mal formado, y rechazó esa idea. Según $ 15.4 / 11:

Una implementación no rechazará una expresión simplemente porque, al ejecutarse, lanza o podría lanzar una excepción que la función que contiene no permite. [Ejemplo:

extern void f() throw(X, Y); void g() throw(X) { f(); // OK }

la llamada a f está bien formada, aunque cuando se llama, f podría lanzar la excepción Y que g no permite. —En ejemplo]

Independientemente de lo que motivó la decisión, o de lo que pudo haber sido, parece bastante claro que esto no fue resultado de un accidente o descuido.

En cuanto a por qué se tomó esta decisión, al menos algunos regresan a la interacción con otras características nuevas de C ++ 11, como la semántica de movimientos.

Mover la semántica puede hacer que la seguridad excepcional (especialmente la garantía sólida) sea mucho más difícil de imponer / proporcionar. Cuando copias, si algo sale mal, es bastante fácil "revertir" la transacción: destruye todas las copias que hayas hecho, libera la memoria y el original permanece intacto. Solo si / cuando la copia tiene éxito, destruyes el original.

Con la semántica de movimiento, esto es más difícil: si obtiene una excepción en medio de mover cosas, cualquier cosa que ya haya movido debe volver a su lugar original para restaurar el orden original, pero si el constructor de movimientos o El operador de asignación de movimiento puede lanzar, podría obtener otra excepción en el proceso de intentar retroceder las cosas para intentar restaurar el objeto original.

Combine esto con el hecho de que C ++ 11 puede generar / mueve constructores de movimiento y operadores de asignación de movimiento automáticamente para algunos tipos (aunque hay una larga lista de restricciones). Estos no necesariamente garantizan contra lanzar una excepción. Si escribes explícitamente un constructor de movimientos, casi siempre quieres asegurarte de que no se lance, y eso suele ser bastante fácil de hacer (ya que normalmente estás "robando" contenido, por lo general solo estás copiando algunos puntos). fácil de hacer sin excepciones). Sin embargo, puede ser mucho más difícil apurarse para la plantilla, incluso para plantillas simples como std:pair . Un par de algo que se puede mover con algo que necesita ser copiado se hace difícil de manejar bien.

Eso significaba que, si hubieran decidido hacer el nothrow (y / o el throw() ) en tiempo de compilación, una cantidad de código desconocida (pero probablemente bastante grande) se habría roto por completo: código que había funcionado bien durante años De repente ni siquiera compilaría con el nuevo compilador.

Junto con esto estaba el hecho de que, aunque no están en desuso, las especificaciones dinámicas de excepción permanecen en el idioma, por lo que iban a terminar aplicando al menos algunas especificaciones de excepción en el tiempo de ejecución.

Entonces, sus elecciones fueron:

  1. Romper mucho código existente
  2. Restrinja la semántica de movimientos para que se apliquen a mucho menos código
  3. Continuar (como en C ++ 03) para hacer cumplir las especificaciones de excepción en el tiempo de ejecución.

Dudo que a alguien le haya gustado alguna de estas opciones, pero la tercera parece ser la última mala.


El problema con la verificación en tiempo de compilación: no es realmente posible de ninguna manera útil. Vea el siguiente ejemplo:

void foo(std::vector<int>& v) noexcept { if (!v.empty()) ++v.at(0); }

¿Puede este código lanzar? Claramente no. ¿Podemos comprobar automáticamente? Realmente no. La forma de Java de hacer cosas como esta es poner el cuerpo en un bloque try-catch, pero no creo que sea mejor que lo que tenemos ahora ...


Una razón es simplemente que la aplicación en tiempo de compilación de las especificaciones de excepción (de cualquier sabor) es una molestia. Esto significa que si agrega código de depuración, es posible que tenga que volver a escribir una jerarquía completa de especificaciones de excepción, incluso si el código que agregó no generará excepciones. Y cuando hayas terminado de depurar, tienes que volver a escribirlos. Si te gusta este tipo de trabajo, deberías estar programando en Java.