Lanzar palabra clave en la firma de la función

199

¿Cuál es la razón técnica por la cual se considera una mala práctica usar la throwpalabra clave C ++ en una firma de función?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
Konstantin
fuente
Vea esta pregunta relacionada reciente: stackoverflow.com/questions/1037575/…
laalto
1
¿ noexceptCambia algo?
Aaron McDaid
12
No hay nada de malo en tener opiniones sobre algo relacionado con la programación. Este criterio de cierre es malo, al menos para esta pregunta. Es una pregunta interesante con respuestas interesantes.
Anders Lindén
Cabe señalar que las especificaciones de excepción están en desuso desde C ++ 11.
François Andrieux

Respuestas:

127

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
fuente
25
Si. Hay mejores formas de agregar espacios en blanco a su código que throw (myEx).
Assaf Lavie
44
Sí, las personas que solo descubren las especificaciones de excepción a menudo suponen que funcionan como en Java, donde el compilador puede aplicarlas. En C ++, eso no sucederá, lo que los hace mucho menos útiles.
jalf
77
¿Pero qué tal el propósito documental entonces? Además, se le informará qué excepciones nunca detectará, incluso si lo intenta.
Anders Lindén
1
@ AndersLindén ¿con qué propósito documental? Si solo desea documentar el comportamiento de su código, simplemente coloque un comentario encima. No estoy seguro de lo que quieres decir con la segunda parte. ¿Excepciones que nunca atraparás incluso si lo intentas?
jalf
44
Creo que el código es poderoso cuando se trata de documentar su código. (Los comentarios pueden mentir). El efecto "documental" al que me refiero es que sabrás con seguridad qué excepciones puedes atrapar y cuáles no.
Anders Lindén
57

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, y Hunc()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.
algo
fuente
1
No estoy seguro de por qué lanzaría una excepción y no podría mencionarla. Incluso si es lanzada por otra función, sé qué excepciones podrían lanzarse. La única razón por la que puedo ver es porque es tedioso.
MasterMastic
3
@Ken: El punto es que escribir especificaciones de excepción tiene principalmente consecuencias negativas. El único efecto positivo es que le muestra al programador qué excepciones pueden ocurrir, pero dado que el compilador no lo verifica de manera razonable, es propenso a errores y, por lo tanto, no vale mucho.
algo
1
Oh bien, gracias por responder. Supongo que para eso sirve la documentación.
MasterMastic
2
Incorrecto. Se debe escribir una especificación de excepción, pero la idea es comunicar qué errores debe intentar detectar la persona que llama.
HelloWorld
1
Justo como dice @StudentT: es responsabilidad de la función garantizar no lanzar más excepciones. Si lo hacen, el programa termina como debería. Declarar lanzamiento significa que no es mi responsabilidad manejar esta situación y la persona que llama debe tener suficiente información para hacerlo. No declarar excepciones significa que pueden ocurrir en cualquier lugar y podrían manejarse en cualquier lugar. Eso es ciertamente un desastre anti-OOP. Es la falla del diseño para detectar excepciones en lugares equivocados. Recomiendo no tirar vacío, ya que las excepciones son excepcionales y la mayoría de las funciones deberían tirar vacío de todos modos.
Jan Turoň
30

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)

John Doe
fuente
12

El único efecto práctico del especificador de lanzamiento es que si myExcsu 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) */ {
}
Paolo Tedesco
fuente
55
También es útil tener en cuenta que una llamada a std :: unexpected () generalmente resulta en una llamada a std :: terminate () y al final abrupto de su programa.
sth
1
- y que MSVC al menos no implementa este comportamiento hasta donde yo sé.
jalf
9

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.

Greg D
fuente
9

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).

Pratik Singhal
fuente
3

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.

Pablo Adames
fuente
1
"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". Esta es la razón # 1 de errores, cuando se comunica con máquinas o personas: la diferencia entre lo que decimos y lo que queremos decir.
Thagomizer