c++ - tipo - try catch en c
¿Qué operaciones no deben lanzar una excepción en C++? (3)
Así que esto no responde perfectamente a tu pregunta (busqué un poco por mi propia curiosidad), pero creo que las funciones / operadores garantizados se originan principalmente en cualquier función de estilo C disponible en C ++, así como en algunas funciones que Son lo suficientemente arbitrarios como para dar tal garantía. En general, no se espera que los programas de C ++ proporcionen esta garantía (¿ Cuándo debería usarse std :: nothrow? ) Y ni siquiera está claro si esa garantía le ofrece algo útil en el código que hace uso regular de las excepciones. No pude encontrar una lista completa de TODAS las funciones de C ++ que no sean funciones de proyección (corríjame si no cumplo con un dictado estándar), aparte de las listas de swaps, destructores y manipulaciones primitivas. También parece bastante raro que una función que no está completamente definida en una biblioteca requiera que el usuario implemente una función nothrows.
Entonces, tal vez para llegar a la raíz de su pregunta, en su mayoría debe asumir que cualquier cosa puede generar C ++ y tomarla como una simplificación cuando encuentre algo que absolutamente no puede lanzar una excepción. Escribir código de excepción segura es muy parecido a escribir código libre de errores: es más difícil de lo que parece y, honestamente, a menudo no vale la pena el esfuerzo. Además, hay muchos niveles entre la excepción de código inseguro y las funciones fuertes de nothrow. Vea esta asombrosa respuesta sobre cómo escribir el código de seguridad de excepción como verificación de estos puntos: ¿(realmente) escribe código de excepción de seguridad? . Hay más información sobre la seguridad de las excepciones en el sitio de impulso http://www.boost.org/community/exception_safety.html .
Para el desarrollo del código, he escuchado opiniones mixtas de profesores y expertos en codificación sobre qué debe y qué no debe lanzar una excepción y qué garantías debe proporcionar dicho código. Pero una afirmación bastante consistente es que el código que puede fácilmente lanzar una excepción debe estar claramente documentado como tal o indicar la capacidad de lanzamiento en la definición de la función (no siempre aplicable a C ++ solo). Las funciones que pueden lanzar una excepción son mucho más comunes que las funciones que nunca se lanzan y saber qué excepciones pueden ocurrir es muy importante. Pero garantizar que una función que divide una entrada por otra nunca arroje una excepción de división por 0 puede ser bastante innecesario / no deseado. Por lo tanto, nothrow puede ser tranquilizador, pero no es necesario o siempre es útil para la ejecución segura de código.
En respuesta a los comentarios sobre la pregunta original:
Las personas a veces dirán que los constructores de lanzamiento de excepciones son malos cuando se lanzan en contenedores o en general, y que siempre se deben usar la inicialización de dos pasos y las verificaciones de validación. Sin embargo, si un constructor falla, muchas veces no se puede arreglar o está en un estado excepcionalmente malo, de lo contrario, el constructor habría resuelto el problema en primer lugar. Comprobar si el objeto es válido es tan difícil como poner un bloque try catch alrededor del código de inicialización para los objetos que sabes que tienen una buena oportunidad de lanzar una excepción. Entonces, ¿cuál es la correcta? Por lo general, lo que se usó en el resto de la base del código, o su preferencia personal. Prefiero el código basado en excepciones, ya que me da una sensación de mayor flexibilidad sin una tonelada de código de equipaje para verificar la validez de cada objeto (otros podrían estar en desacuerdo).
¿Dónde deja esto la pregunta original y las extensiones enumeradas en los comentarios? Bueno, a partir de las fuentes proporcionadas y de mi propia experiencia, preocuparme por no ver las funciones en una perspectiva de "Seguridad de Excepción" de C ++ es a menudo el enfoque incorrecto para manejar el desarrollo de código. En vez de eso, tenga en cuenta las funciones que sabe que podrían lanzar razonablemente una excepción y manejar esos casos de manera adecuada. Por lo general, esto implica operaciones de IO en las que no tiene control total sobre lo que provocaría la excepción. Si obtiene una excepción que nunca esperó o que no creía posible, entonces tiene un error en su lógica (o en sus suposiciones sobre los usos de la función) y deberá corregir el código fuente para adaptarse. Tratar de ofrecer garantías sobre el código que no es trivial (y en ocasiones incluso en ese caso) es como decir que un servidor nunca se bloqueará: puede ser muy estable, pero probablemente no esté 100% seguro.
Hoy aprendí que el swap
no tiene permitido lanzar una excepción en C ++.
También sé que lo siguiente tampoco puede lanzar excepciones:
- Destructores
- Lectura / escritura de tipos primitivos.
¿Hay otros?
O tal vez, ¿hay algún tipo de lista que menciona todo lo que no puede lanzar?
(Algo más sucinto que el estándar en sí, obviamente).
Hay una gran diferencia entre no puede y no debe . Las operaciones en tipos primitivos no se pueden lanzar, como muchas funciones y funciones miembro, incluidas muchas operaciones en la biblioteca estándar y / o muchas otras bibliotecas.
Ahora en el caso de que no , puedes incluir destructores y swap. Dependiendo de cómo los implemente, ellos pueden lanzar, pero debe evitar tener destructores que lanzan, y en el caso de swap
, proporcionar una operación de swap con la garantía de no tirar es la forma más sencilla de lograr la fuerte garantía de excepción en su clase, como puede copiar a un lado, realice la operación en la copia y luego intercambie con el original.
Pero tenga en cuenta que el lenguaje permite tanto a los destructores como al swap
lanzar. swap
puede lanzar, en el caso más simple si no lo sobrecargas, entonces std::swap
realiza una construcción de copia, una asignación y una destrucción, tres operaciones que pueden lanzar una excepción (dependiendo de tus tipos).
Las reglas para los destructores han cambiado en C ++ 11, lo que significa que un destructor sin una especificación de excepción tiene una especificación implícita de noexcept
que a su vez significa que si lanzó una excepción, el tiempo de ejecución llamará a terminate
, pero puede cambiar la especificación de la excepción a noexcept(false)
y luego el destructor también puede lanzar.
Al final del día, no puede proporcionar garantías de excepción sin comprender su base de código, porque casi todas las funciones en C ++ pueden lanzar.
Si desea una respuesta detallada a esta pregunta, vaya a http://exceptionsafecode.com/ y vea el video de 85 minutos que cubre solo C ++ 03 o el video de tres horas (en dos partes) que cubre ambos C ++ 03 y C ++ 11.
Al escribir el código de excepción segura, asumimos que todas las funciones se lanzan, a menos que sepamos algo diferente.
En breve,
*) Los tipos fundamentales (incluidas las matrices y los punteros a) se pueden asignar ay desde, y se pueden usar con operaciones que no involucren operadores definidos por el usuario (matemáticas que usan solo números enteros fundamentales y valores de punto flotante, por ejemplo). Tenga en cuenta que la división por cero (o cualquier expresión cuyo resultado no se haya definido matemáticamente) es un comportamiento indefinido y puede o no arrojarse dependiendo de la implementación.
*) Destructores: no hay nada conceptualmente incorrecto con los destructores que emiten excepciones, ni el estándar los prohíbe. Sin embargo, las buenas pautas de codificación generalmente las prohíben porque el lenguaje no admite muy bien este escenario. (Por ejemplo, si se lanzan destructores de objetos en contenedores STL, el comportamiento no está definido).
*) El uso de swap () es una técnica importante para proporcionar una garantía de excepción sólida, pero solo si swap () no es lanzable. En general, no podemos asumir que swap () no es lanzable, pero el video trata sobre cómo crear un intercambio no lanzable para sus tipos definidos por el usuario en C ++ 03 y C ++ 11.
*) C ++ 11 introduce movimientos semánticos y operaciones de movimiento. En C ++ 11, swap () se implementa utilizando la semántica de movimiento y la situación con las operaciones de movimiento es similar a la situación con swap (). No podemos asumir que las operaciones de movimiento no se lanzan, pero generalmente podemos crear operaciones de movimiento no lanzadas para los tipos definidos por el usuario que creamos (y se proporcionan para los tipos de biblioteca estándar). Si proporcionamos operaciones de movimientos no lanzados en C ++ 11, obtendremos swap () no lanzados de forma gratuita, pero podemos elegir implementar nuestro propio swap () de cualquier manera para fines de rendimiento. Nuevamente, esta es la portada en detalle en el video.
*) C ++ 11 introduce el operador noexcept y el decorador de funciones. (La especificación "throw ()" de Classic C ++ ahora está en desuso). También proporciona la función de introspección para que el código pueda escribirse para manejar situaciones de manera diferente dependiendo de si existen o no operaciones de no lanzar.
Además de los videos, el sitio web exceptionsafecode.com tiene una bibliografía de libros y artículos sobre excepciones que deben actualizarse para C ++ 11.