2009-06-28 9 views
154

¿Cuál es la razón técnica por la que se considera una mala práctica utilizar la palabra clave C++ throw en una firma de función?Lanzar palabra clave en la firma de la función

bool some_func() throw(myExc) 
{ 
    ... 
    if (problem_occurred) 
    { 
    throw myExc("problem occurred"); 
    } 
    ... 
} 
+0

ver este reciente pregunta relacionada: http://stackoverflow.com/questions/1037575/why-arent-exceptions-in-c-checked-by-the-compiler – laalto

+0

Y http://stackoverflow.com/ questions/88573/should-i-use-an-exception-specifier-in-c – akauppi

+1

¿'noexcept' cambia algo? –

Respuesta

98

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 muchos más detalles acerca de por qué, pero el problema es en parte que el compilador no puede hacer cumplir esto, por lo que se debe comprobar en tiempo de ejecución, lo 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.

+17

Sí. Hay mejores formas de agregar espacios en blanco a su código que throw (myEx). –

+0

No se trata de no poder aplicarlo en tiempo de compilación. Los compiladores de Java pueden aplicarlo, después de todo. Es que no se supone que se aplicará en tiempo de compilación. La función está completamente permitida para lanzar otras excepciones, pero si lo hace, entonces debe llamarse a unexpected(). –

+0

Er, a menos que quiera decir que los compiladores C++ no pueden aplicarlo porque el estándar les prohíbe hacerlo. –

7

Cuando se agregaron las especificaciones de tiro al idioma, fue con las mejores intenciones, pero la práctica ha confirmado una mayor enfoque práctico.

con C++, mi regla general es que sólo las especificaciones de uso de tiro para indicar que un método no puede lanzar. Esta es una fuerte garantía. de lo contrario, se supone que podría tirar nada.

8

El El único efecto práctico del especificador de tiro es que si su función arroja algo diferente de myExc, se llamará std::unexpected (inste anuncio del mecanismo de excepción no controlado normal).

para documentar el tipo de excepciones que una función puede lanzar, Me suelen hacer esto:

bool 
some_func() /* throw (myExc) */ { 
} 
+2

También es útil tener en cuenta que una llamada a std :: unexpected() generalmente da como resultado una llamada a std :: terminate() y el final abrupto de su programa. – sth

+0

- y que MSVC al menos no implementa este comportamiento hasta donde yo sé. – jalf

39

Jalf ya ligado a ella, pero la GOTW pone bastante bien por qué especificaciones de excepción no son tan útiles como uno podría esperar:

int Gunc() throw(); // will throw nothing (?) 
int Hunc() throw(A,B); // can only throw A or B (?) 

son los comentarios correcta? No exactamente. Gunc() de hecho puede tirar algo, y Hunc() bien puede tirar algo que no sea A o B! El compilador simplemente garantiza golpearlos sin sentido si lo hacen ... ah, y para golpear tu programa sin sentido también, la mayoría del tiempo.

Eso es justo lo que se reduce a, es probable que sólo va a terminar con una llamada a terminate() y su programa de morir una muerte rápida, pero doloroso.

La conclusión GOTWs es:

Así que aquí está lo que parece ser el mejor consejo que como comunidad hemos aprendido a partir de hoy:

  • Moral # 1: nunca escribir una excepción especificación.
  • Moral # 2: Excepto posiblemente uno vacío, pero si yo fuera tú, evitaría incluso eso.
+1

No estoy seguro de por qué lanzaría una excepción y no podría mencionarla. Incluso si es arrojado por otra función, sé qué excepciones podrían arrojarse. La única razón por la que puedo ver es porque es tedioso. – MasterMastic

+2

@Ken: El punto es que escribir especificaciones de excepción tiene consecuencias principalmente negativas. El único efecto positivo es que muestra al programador qué excepciones pueden ocurrir, pero como el compilador no lo verifica de manera razonable, es propenso a errores y, por lo tanto, no vale mucho. – sth

+1

Oh bien, gracias por responder. Supongo que para eso sirve la documentación. – MasterMastic

7

Bueno, mientras que buscando en Google acerca de esta especificación de tiro, tenía un vistazo a este artículo: - (http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx)

reproduzco una parte aquí también, de modo que pueda ser utilizado en futuro independientemente del hecho de que el enlace de arriba funciona 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 "tiro()" atributo, el compilador puede optimizar por completo la "barra" variables de distancia, porque sabe que no hay manera para que una excepción sea lanzada fuera del MethodThatCannotThrow(). Sin el atributo throw(), el compilador tiene que crear la variable "bar", porque si MethodThatCannotThrow arroja una excepción, el manejador de excepciones puede/dependerá del valor de la variable de 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 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).

17

Para agregar un poco más de valor a todas las otras respuestas a esta pregunta, uno debe invertir unos minutos en la pregunta: ¿Cuál es la salida 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 ha señalado here, el programa llama std :: terminar() y por lo tanto ninguno de los controladores de excepciones será llamado. Detalles: se llama a la función my_unexpected() pero, como no se vuelve a lanzar un tipo de excepción coincidente para el prototipo de función throw_exception(), al final se llama a std :: terminate(). Por lo que la salida completa se ve así:

usuario @ usuario: ~/tmp $ g ++ -o except.test except.test.cpp
usuario @ usuario: ~/tmp $ ./except.test
bien - esto era inesperado
terminar llamada después de lanzar una instancia de 'int'
Aborted (núcleo objeto de dumping)

2

a ninguna especificación de banda en una función inline que sólo devuelve una variable miembro y no podría lanzar excepciones puede ser utilizado por algunos compiladores para pesimizaciones (el pposite de optimizaciones) que pueden tener un efecto perjudicial en el rendimiento. Esto se describe en la literatura Boost: Exception-specification

Con algunos compiladores una ninguna especificación de banda en funciones no en línea puede ser beneficioso si las optimizaciones correctas se hacen y el uso de que afecta al rendimiento de la función de una manera que se justifica eso.

Para mí, parece que 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 los que tienen prisa:

razón de excepción de especificación

especificaciones de excepción [ISO 15,4] a veces están codificados para indicar qué excepciones puede ser arrojado, o porque el programador espera que mejoren el rendimiento. Pero considere el siguiente miembro desde un puntero inteligente:

T & operador *() const throw() {return * ptr; }

Esta función no llama a otras funciones; solo manipula tipos de datos fundamentales, como punteros. Por lo tanto, nunca se puede invocar el comportamiento en 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 en base a la especificación de excepción vacía. Un compilador "tonto", sin embargo, puede hacer todo tipo de pesimizaciones.

Por ejemplo, algunos compiladores se apagan en línea 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 no se pueda usar en aplicaciones prácticas.

Aunque inicialmente atractivo, una especificación de excepción tiende a tener consecuencias que requieren una comprensión muy cuidadosa para comprender. El mayor problema con las especificaciones de excepción es que los programadores las usan como si tuvieran el efecto deseado por el programador, en lugar del efecto que realmente tienen.

Una función no en línea es el único lugar donde una especificación de excepción "no arroja nada" puede tener algún beneficio con algunos compiladores.

Cuestiones relacionadas