¿Cuál es la razón técnica por la cual se considera una mala práctica usar la throw
palabra clave C ++ en una firma de función?
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
No, no se considera una buena práctica. Por el contrario, generalmente se considera una mala idea.
http://www.gotw.ca/publications/mill22.htm entra en más detalles sobre por qué, pero el problema es en parte que el compilador no puede hacer cumplir esto, por lo que debe verificarse en tiempo de ejecución, que generalmente es indeseable. Y no está bien soportado en ningún caso. (MSVC ignora las especificaciones de excepción, excepto throw (), que interpreta como una garantía de que no se lanzará ninguna excepción.
Jalf ya está vinculado a él, pero GOTW lo explica muy bien por qué las especificaciones de excepción no son tan útiles como cabría esperar:
int Gunc() throw(); // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)
¿Son correctos los comentarios? No exactamente.
Gunc()
¡de hecho puede arrojar algo, yHunc()
bien puede arrojar algo diferente a A o B! El compilador solo garantiza vencerlos sin sentido si lo hacen ... oh, y vencer a su programa también sin sentido, la mayoría de las veces.
De eso se trata, probablemente terminarás con una llamada terminate()
y tu programa morirá una muerte rápida pero dolorosa.
La conclusión de GOTW es:
Así que aquí está lo que parece ser el mejor consejo que como comunidad hemos aprendido hoy:
- Moraleja # 1: Nunca escriba una especificación de excepción.
- Moraleja # 2: Excepto posiblemente uno vacío, pero si yo fuera tú, incluso evitaría eso.
Para agregar un poco más de valor a todas las otras respuestas a esta pregunta, uno debería invertir unos minutos en la pregunta: ¿Cuál es el resultado del siguiente código?
#include <iostream>
void throw_exception() throw(const char *)
{
throw 10;
}
void my_unexpected(){
std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
std::set_unexpected(my_unexpected);
try{
throw_exception();
}catch(int x){
std::cout << "catch int: " << x << std::endl;
}catch(...){
std::cout << "catch ..." << std::endl;
}
}
Respuesta: Como se señaló aquí , el programa llamastd::terminate()
y, por lo tanto, no se ninguno de los controladores de excepciones.
Detalles: my_unexpected()
se llama a la primera función, pero dado que no se vuelve a lanzar un tipo de excepción coincidente para el throw_exception()
prototipo de la función, al final std::terminate()
se llama. Entonces la salida completa se ve así:
user @ user: ~ / tmp $ g ++ -o except.test except.test.cpp
user @ user: ~ / tmp $ ./except.test
well - esto fue una
terminación inesperada llamada después de lanzar una instancia de 'int'
Aborted (core objeto de dumping)
El único efecto práctico del especificador de lanzamiento es que si myExc
su función arroja algo diferente destd::unexpected
se llamará (en lugar del mecanismo de excepción normal no controlado).
Para documentar el tipo de excepciones que puede lanzar una función, normalmente hago esto:
bool
some_func() /* throw (myExc) */ {
}
Cuando se agregaron especificaciones de lanzamiento al lenguaje, fue con las mejores intenciones, pero la práctica ha dado lugar a un enfoque más práctico.
Con C ++, mi regla general es usar solo especificaciones de lanzamiento para indicar que un método no puede lanzar. Esta es una fuerte garantía. De lo contrario, suponga que podría arrojar cualquier cosa.
Bueno, mientras busqué en Google esta especificación de lanzamiento, eché un vistazo a este artículo: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )
También estoy reproduciendo una parte de él aquí, para que pueda usarse en el futuro independientemente del hecho de que el enlace anterior funcione o no.
class MyClass
{
size_t CalculateFoo()
{
:
:
};
size_t MethodThatCannotThrow() throw()
{
return 100;
};
void ExampleMethod()
{
size_t foo, bar;
try
{
foo = CalculateFoo();
bar = foo * 100;
MethodThatCannotThrow();
printf("bar is %d", bar);
}
catch (...)
{
}
}
};
Cuando el compilador ve esto, con el atributo "throw ()", el compilador puede optimizar completamente la variable "bar", porque sabe que no hay forma de que se arroje una excepción desde MethodThatCannotThrow (). Sin el atributo throw (), el compilador tiene que crear la variable "bar", porque si MethodThatCannotThrow lanza una excepción, el controlador de excepciones puede / dependerá del valor de la variable de la barra.
Además, las herramientas de análisis de código fuente como prefast pueden (y usarán) la anotación throw () para mejorar sus capacidades de detección de errores; por ejemplo, si tiene un try / catch y todas las funciones que llama están marcadas como throw (), no necesita el try / catch (sí, esto tiene un problema si luego llama a una función que podría lanzar).
Algunos compiladores pueden usar una especificación de no lanzar en una función en línea que solo devuelve una variable miembro y no podría lanzar excepciones para hacer pesimizaciones (una palabra inventada para el opuesto de optimizaciones) que pueden tener un efecto perjudicial en el rendimiento. Esto se describe en la literatura de Boost: especificación de excepción
Con algunos compiladores una especificación de no tirar sobre funciones no en línea puede ser beneficiosa si se realizan las optimizaciones correctas y el uso de esa función impacta el rendimiento de una manera que lo justifique.
Para mí, parece que si usarlo o no es una llamada hecha por un ojo muy crítico como parte de un esfuerzo de optimización del rendimiento, tal vez usando herramientas de creación de perfiles.
Una cita del enlace anterior para aquellos que tienen prisa (contiene un ejemplo de los malos efectos no deseados de especificar throw en una función en línea desde un compilador ingenuo):
Justificación de la excepción-especificación
Las especificaciones de excepción [ISO 15.4] a veces se codifican para indicar qué excepciones se pueden lanzar, o porque el programador espera que mejoren el rendimiento. Pero considere el siguiente miembro de un puntero inteligente:
T & operator * () const throw () {return * ptr; }
Esta función no llama a otras funciones; solo manipula tipos de datos fundamentales como punteros. Por lo tanto, no se puede invocar ningún comportamiento de tiempo de ejecución de la especificación de excepción. La función está completamente expuesta al compilador; de hecho, se declara en línea. Por lo tanto, un compilador inteligente puede deducir fácilmente que las funciones son incapaces de lanzar excepciones, y hacer las mismas optimizaciones que habría hecho basándose en la especificación de excepción vacía. Sin embargo, un compilador "tonto" puede hacer todo tipo de pesimizaciones.
Por ejemplo, algunos compiladores desactivan la alineación si hay una especificación de excepción. Algunos compiladores agregan bloques try / catch. Tales pesimizaciones pueden ser un desastre de rendimiento que hace que el código sea inutilizable en aplicaciones prácticas.
Aunque inicialmente es atractivo, una especificación de excepción tiende a tener consecuencias que requieren un pensamiento muy cuidadoso para entender. El mayor problema con las especificaciones de excepción es que los programadores las usan como si tuvieran el efecto que le gustaría al programador, en lugar del efecto que realmente tienen.
Una función no en línea es el lugar donde una especificación de excepción "no arroja nada" puede tener algún beneficio con algunos compiladores.
noexcept
Cambia algo?