excepción de c ++: lanzando std :: string

80

Me gustaría lanzar una excepción cuando mis métodos de C ++ encuentran algo extraño y no se pueden recuperar. ¿Está bien lanzar un std::stringpuntero?

Esto es lo que estaba deseando hacer:

void Foo::Bar() {
    if(!QueryPerformanceTimer(&m_baz)) {
        throw new std::string("it's the end of the world!");
    }
}

void Foo::Caller() {
    try {
        this->Bar(); // should throw
    }
    catch(std::string *caught) { // not quite sure the syntax is OK here...
        std::cout << "Got " << caught << std::endl;
    }
}
Palad1
fuente
23
Sería legal, pero no moral.
Marcin
18
Tiene una pérdida de memoria. ¿Quién está eliminando el puntero de cadena arrojado? No use punteros para excepciones.
fnieto - Fernando Nieto
2
Sé que es un poco tarde, pero de todos modos, este artículo tiene varios puntos sobre este tema boost.org/community/error_handling.html
Alex Kreimer

Respuestas:

100

Si. std::exceptiones la clase de excepción base en la biblioteca estándar de C ++. Es posible que desee evitar el uso de cadenas como clases de excepción porque ellas mismas pueden lanzar una excepción durante el uso. Si eso sucede, ¿dónde estarás?

boost tiene un excelente documento sobre buen estilo para excepciones y manejo de errores. Vale la pena leerlo.

christopher_f
fuente
20
Nota al margen: std :: terminate se llamará si se lanza el objeto de excepción, ahí es donde estarás (¡y no es bonito!)
Alaric
6
Consulte gotw.ca/publications/mill16.htm para ver un argumento sobre por qué preocuparse por las asignaciones que arrojan excepciones es una pérdida de tiempo. Otro argumento en contra de esta respuesta es que std :: runtime_exception y family lo hacen, así que ¿por qué no?
Greg Rogers
63

Algunos principios:

  1. tiene una clase base std :: exception, debe tener sus excepciones derivadas de ella. De esa manera, el manejador de excepciones general todavía tiene algo de información.

  2. No arrojes punteros sino objeciones, de esa manera se maneja la memoria por ti.

Ejemplo:

struct MyException : public std::exception
{
   std::string s;
   MyException(std::string ss) : s(ss) {}
   ~MyException() throw () {} // Updated
   const char* what() const throw() { return s.c_str(); }
};

Y luego úsalo en tu código:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw MyException("it's the end of the world!");
  }
}

void Foo::Caller(){
  try{
    this->Bar();// should throw
  }catch(MyException& caught){
    std::cout<<"Got "<<caught.what()<<std::endl;
  }
}
PierreBdR
fuente
5
¿No sería mejor derivar de std :: runtime_exception?
Martin York
Tenga en cuenta que el argumento de christopher_f sigue siendo válido: su excepción podría arrojar una excepción en la construcción ... Alimento para el pensamiento, supongo ... :-D ... podría estar equivocado, pero se supone que la excepción debe detectarse a través de su constante- ¿Numero de referencia?
paercebal
Para la referencia constante, es posible, pero no obligatorio. Me pregunté un rato al respecto ... no encontré ninguna referencia a favor o en contra.
PierreBdR
const ref aquí solo es útil para que no modifique accidentalmente la excepción en el bloque catch. lo que no harás de todos modos, así que solo
Probé
24

Todos estos funcionan:

#include <iostream>
using namespace std;

//Good, because manual memory management isn't needed and this uses
//less heap memory (or no heap memory) so this is safer if
//used in a low memory situation
void f() { throw string("foo"); }

//Valid, but avoid manual memory management if there's no reason to use it
void g() { throw new string("foo"); }

//Best.  Just a pointer to a string literal, so no allocation is needed,
//saving on cleanup, and removing a chance for an allocation to fail.
void h() { throw "foo"; }

int main() {
  try { f(); } catch (string s) { cout << s << endl; }
  try { g(); } catch (string* s) { cout << *s << endl; delete s; }
  try { h(); } catch (const char* s) { cout << s << endl; }
  return 0;
}

Debería preferir h af af g. Tenga en cuenta que, en la opción menos preferible, debe liberar la memoria de forma explícita.

Patrick M
fuente
1
Pero, ¿no es const charun error lanzar un puntero a una variable local? Sí, por supuesto, sé que el compilador colocará la cadena c en la sección no modificada que no cambiará la dirección hasta que se ejecute una aplicación. Pero no está definido; más aún, ¿qué sería si este código estuviera en una biblioteca que desapareció justo después de arrojar un error? Por cierto, yo también hice muchas cosas malas en mi proyecto, solo soy un estudiante. Pero debería haberlo pensado ...
Hi-Angel
1
@ Hi-Angel No hay una variable local; lo que se arroja allí es un literal de cadena, que tiene un tratamiento específico y bien definido por el Estándar en términos de vida útil, y sus preocupaciones son discutibles. Ver, por ejemplo, stackoverflow.com/a/32872550/2757035 Si hubiera un problema aquí, básicamente ningún lanzamiento de mensajes podría funcionar (al menos no sin requerir acrobacias / riesgos adicionales indebidos), por lo que, por supuesto, no lo hay, y está bien .
underscore_d
8

Funciona, pero no lo haría si fuera tú. No parece que esté eliminando esos datos del montón cuando haya terminado, lo que significa que ha creado una pérdida de memoria. El compilador de C ++ se encarga de garantizar que los datos de excepción se mantengan vivos incluso cuando se abre la pila, por lo que no sienta que necesita usar el montón.

Por cierto, lanzar un std::stringno es el mejor enfoque para empezar. Tendrá mucha más flexibilidad en el futuro si usa un objeto contenedor simple. Es posible que solo encapsule un stringpor ahora, pero tal vez en el futuro desee incluir otra información, como algunos datos que causaron la excepción o tal vez un número de línea (muy común, eso). No desea cambiar todo el manejo de excepciones en cada lugar de su base de código, así que tome el camino correcto ahora y no arroje objetos crudos.

Daniel Spiewak
fuente
8

Además de probablemente lanzar algo derivado de std :: exception, debería lanzar temporales anónimos y capturar por referencia:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw std::string("it's the end of the world!");
  }
}

void Foo:Caller(){
  try{
    this->Bar();// should throw
  }catch(std::string& caught){ // not quite sure the syntax is ok here...
    std::cout<<"Got "<<caught<<std::endl;
  }
}
  • Debe lanzar temporales anónimos para que el compilador se ocupe de la vida útil del objeto de lo que sea que esté lanzando; si arroja algo nuevo del montón, alguien más necesita liberarlo.
  • Debes captar referencias para evitar que los objetos se corten

.

Consulte "Effective C ++ - 3rd edition" de Meyer para obtener más detalles o visite https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference

Michael Burr
fuente
5

La forma más sencilla de lanzar una excepción en C ++:

#include <iostream>
using namespace std;
void purturb(){
    throw "Cannot purturb at this time.";
}
int main() {
    try{
        purturb();
    }
    catch(const char* msg){
        cout << "We caught a message: " << msg << endl;
    }
    cout << "done";
    return 0;
}

Esto imprime:

We caught a message: Cannot purturb at this time.
done

Si detecta la excepción lanzada, la excepción está contenida y el programa continuará. Si no detecta la excepción, entonces el programa existe e imprime:

This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.

Eric Leschinski
fuente
3
Esto parece una mala idea catch (std::exception&), no lo entenderé.
Timmmm
1

Aunque esta pregunta es bastante antigua y ya ha sido respondida, solo quiero agregar una nota sobre cómo hacer un manejo adecuado de excepciones en C ++ 11 :

Utilice std::nested_exceptionystd::throw_with_nested

El uso de estos, en mi opinión, conduce a un diseño de excepción más limpio y hace que sea innecesario crear una jerarquía de clases de excepción.

Tenga en cuenta que esto le permite obtener un seguimiento de las excepciones dentro de su código sin necesidad de un depurador o un registro engorroso. Se describe en StackOverflow aquí y aquí , cómo escribir un controlador de excepciones adecuado que volverá a generar excepciones anidadas.

Dado que puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a dicho rastreo! También puede echar un vistazo a mi MWE en GitHub , donde un backtrace se vería así:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
GPMueller
fuente