En C ++, ¿cuáles son los beneficios de usar excepciones y try / catch en lugar de simplemente devolver un código de error?

StackOverflow https://stackoverflow.com/questions/196522

Pregunta

He programado C y C ++ durante mucho tiempo y hasta ahora nunca he usado excepciones y try / catch. ¿Cuáles son los beneficios de usar eso en lugar de que las funciones devuelvan códigos de error?

¿Fue útil?

Solución

Posiblemente un punto obvio: un desarrollador puede ignorar (o no ser consciente de) su estado de devolución y continuar felizmente sin saber que algo falló.

Una excepción debe ser reconocida de alguna manera: no se puede ignorar silenciosamente sin implementar activamente algo para hacerlo.

Otros consejos

La ventaja de las excepciones es doble:

  • No se pueden ignorar. Debe tratar con ellos en algún nivel, o terminarán su programa. Con el código de error, debe verificarlos explícitamente o se perderán.

  • Se pueden ignorar. Si no se puede resolver un error en un nivel, automáticamente pasará al siguiente nivel, donde puede estar. Los códigos de error deben pasarse explícitamente hasta que alcancen el nivel en el que se puede tratar.

La ventaja es que no tiene que verificar el código de error después de cada llamada potencialmente fallida. Sin embargo, para que esto funcione, debe combinarlo con clases RAII para que todo se limpie automáticamente a medida que la pila se desenrolla.

Con mensajes de error:

int DoSomeThings()
{
    int error = 0;
    HandleA hA;
    error = CreateAObject(&ha);
    if (error)
       goto cleanUpFailedA;

    HandleB hB;
    error = CreateBObjectWithA(hA, &hB);
    if (error)
       goto cleanUpFailedB;

    HandleC hC;
    error = CreateCObjectWithA(hB, &hC);
    if (error)
       goto cleanUpFailedC;

    ...

    cleanUpFailedC:
       DeleteCObject(hC);
    cleanUpFailedB:
       DeleteBObject(hB);
    cleanUpFailedA:
       DeleteAObject(hA);

    return error;
}

Con excepciones y RAII

void DoSomeThings()
{
    RAIIHandleA hA = CreateAObject();
    RAIIHandleB hB = CreateBObjectWithA(hA);
    RAIIHandleC hC = CreateCObjectWithB(hB);
    ...
}

struct RAIIHandleA
{
    HandleA Handle;
    RAIIHandleA(HandleA handle) : Handle(handle) {}
    ~RAIIHandleA() { DeleteAObject(Handle); }
}
...

A primera vista, la versión RAII / Exceptions parece más larga, hasta que te das cuenta de que el código de limpieza solo debe escribirse una vez (y hay formas de simplificarlo). Pero la segunda versión de DoSomeThings es mucho más clara y fácil de mantener.

NO intente usar excepciones en C ++ sin el lenguaje RAII, ya que perderá recursos y memoria. Toda su limpieza debe hacerse en destructores de objetos asignados a la pila.

Me doy cuenta de que hay otras formas de manejar el código de error, pero todas terminan pareciéndose lo mismo. Si sueltas los gotos, terminas repitiendo el código de limpieza.

Un punto para los códigos de error es que hacen obvio dónde pueden fallar las cosas y cómo pueden fallar. En el código anterior, lo escribe asumiendo que las cosas no fallarán (pero si lo hacen, estará protegido por los envoltorios RAII). Pero terminas prestando menos atención a dónde las cosas pueden salir mal.

El manejo de excepciones es útil porque facilita la separación del código de manejo de errores del código escrito para manejar la función del programa. Esto facilita la lectura y escritura del código.

Aparte de las otras cosas que se mencionaron, no puede devolver un código de error de un constructor. Destructores tampoco, pero también debes evitar lanzar una excepción desde un destructor.

  • devuelve un código de error cuando una condición de error es esperada en algunos casos
  • lanzar una excepción cuando una condición de error es no esperada en cualquier caso

en el primer caso, la persona que llama de la función debe verificar el código de error para la falla esperada; en este último caso, la excepción puede ser manejada por cualquier persona que llama desde la pila (o el controlador predeterminado) según corresponda

Escribí una entrada de blog sobre esto ( Excepciones hacen para Elegant Code ), que posteriormente se publicó en Sobrecarga . ¡En realidad escribí esto en respuesta a algo que dijo Joel en el podcast de StackOverflow!

De todos modos, creo firmemente que las excepciones son preferibles a los códigos de error en la mayoría de las circunstancias. Me resulta muy doloroso usar funciones que devuelven códigos de error: debe verificar el código de error después de cada llamada, lo que puede interrumpir el flujo del código de llamada. También significa que no puede usar operadores sobrecargados ya que no hay forma de señalar el error.

El dolor de comprobar los códigos de error significa que las personas a menudo descuidan hacerlo, lo que los hace completamente inútiles: al menos debe ignorar explícitamente las excepciones con una declaración catch .

El uso de destructores en C ++ y eliminadores en .NET para garantizar que los recursos se liberen correctamente en presencia de excepciones también puede simplificar enormemente el código. Para obtener el mismo nivel de protección con códigos de error, necesita muchas declaraciones if , muchos códigos de limpieza duplicados o llamadas goto a un bloque común de limpieza en El final de una función. Ninguna de estas opciones es agradable.

Aquí hay una buena explicación de EAFP (" más fácil pedir perdón que Permiso. ''), Que creo que se aplica aquí incluso si se trata de una página de Python en Wikipedia. El uso de excepciones conduce a un estilo de codificación más natural, IMO, y en la opinión de muchos otros también.

Cuando solía enseñar C ++, nuestra explicación estándar era que le permitían evitar enredarse en escenarios de días soleados y lluviosos. En otras palabras, podría escribir una función como si todo funcionara bien, y capturar la excepción al final.

Sin excepciones, tendría que obtener un valor de retorno de cada llamada y asegurarse de que sigue siendo legítimo.

Un beneficio relacionado, por supuesto, es que no "desperdicia" su valor de retorno en excepciones (y, por lo tanto, permite que los métodos que deberían ser nulos sean nulos), y también puede devolver errores de constructores y destructores.

La Guía de estilo C ++ de Google tiene una excelente y completa Análisis de los pros y los contras del uso de excepciones en el código C ++. También indica algunas de las preguntas más importantes que debe hacer; es decir, ¿tengo la intención de distribuir mi código a otras personas (que pueden tener dificultades para integrarse con una base de código habilitada para excepciones)?

A veces, realmente tiene que usar una excepción para marcar un caso excepcional. Por ejemplo, si algo sale mal en un constructor y encuentra que tiene sentido notificar a la persona que llama sobre esto, entonces no tiene más remedio que lanzar una excepción.

Otro ejemplo: a veces no hay ningún valor que su función pueda devolver para denotar un error; cualquier valor que pueda devolver la función denota éxito.

int divide(int a, int b)
{
    if( b == 0 )
        // then what?  no integer can be used for an error flag!
    else
        return a / b;
}

El hecho de que tenga que reconocer las excepciones es correcto, pero esto también se puede implementar utilizando estructuras de error. Puede crear una clase de error base que verifique en su dtor si se ha llamado a un determinado método (por ejemplo, IsOk). Si no, puede registrar algo y luego salir, o lanzar una excepción, o hacer una afirmación, etc. ...

Simplemente llamar al IsOk en el objeto de error sin reaccionar a él, sería el equivalente de escribir catch (...) {} Ambas declaraciones mostrarían la misma falta de buena voluntad del programador.

El transporte del código de error hasta el nivel correcto es una preocupación mayor. Básicamente, debería hacer que casi todos los métodos devuelvan un código de error por el único motivo de propagación. Pero, de nuevo, una función o método siempre debe ser anotado con las excepciones que puede generar. Entonces, básicamente, tiene el mismo problema, sin una interfaz que lo admita.

Como señaló @Martin, lanzar excepciones obliga al programador a manejar el error. Por ejemplo, no verificar los códigos de retorno es una de las mayores fuentes de agujeros de seguridad en los programas en C. Las excepciones aseguran que maneje el error (con suerte) y proporcione algún tipo de ruta de recuperación para su programa. Y si elige ignorar una excepción en lugar de introducir un agujero de seguridad, su programa se bloquea.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top