Procedimientos recomendados de C++ modernos para excepciones y control de errores

En C++ moderno, en la mayoría de los escenarios, la manera preferida de notificar y controlar los errores lógicos y los errores en tiempo de ejecución es usar excepciones. Es especialmente cierto cuando la pila puede contener varias llamadas de función entre la función que detecta el error y la función que tiene el contexto para controlar el error. Las excepciones proporcionan una manera formal y bien definida de código que detecta errores para pasar la información a la pila de llamadas.

Uso de excepciones para código excepcional

Los errores de programa a menudo se dividen en dos categorías: errores lógicos causados por errores de programación, por ejemplo, un error de "índice fuera del intervalo". Y, los errores en tiempo de ejecución que están fuera del control del programador, por ejemplo, un error de "servicio de red no disponible". En la programación de estilo C y en COM, los informes de errores se administran devolviendo un valor que representa un código de error o un código de estado para una función determinada, o estableciendo una variable global que el autor de la llamada puede recuperar opcionalmente después de cada llamada de función para ver si se notificaron errores. Por ejemplo, la programación COM usa el valor devuelto HRESULT para comunicar errores al autor de la llamada. Y la API win32 tiene la GetLastError función para recuperar el último error notificado por la pila de llamadas. En ambos casos, es el autor de la llamada quien debe reconocer el código y responder a él correctamente. Si el autor de la llamada no controla explícitamente el código de error, el programa podría bloquearse sin previo aviso. O bien, puede seguir ejecutando con datos incorrectos y generar resultados incorrectos.

Las excepciones se prefieren en C++ moderno por los siguientes motivos:

  • Una excepción obliga a llamar al código para reconocer una condición de error y controlarla. Las excepciones no controladas detienen la ejecución del programa.

  • Una excepción salta al punto de la pila de llamadas que puede controlar el error. Las funciones intermedias pueden permitir que se propague la excepción. No tienen que coordinarse con otras capas.

  • El mecanismo de desenredado de la pila de excepciones destruye todos los objetos del ámbito después de que se produce una excepción, según reglas bien definidas.

  • Una excepción permite una separación limpia entre el código que detecta el error y el código que controla el error.

En el ejemplo simplificado siguiente se muestra la sintaxis necesaria para iniciar y detectar excepciones en C++.

#include <stdexcept>
#include <limits>
#include <iostream>

using namespace std;

void MyFunc(int c)
{
    if (c > numeric_limits< char> ::max())
        throw invalid_argument("MyFunc argument too large.");
    //...
}

int main()
{
    try
    {
        MyFunc(256); //cause an exception to throw
    }

    catch (invalid_argument& e)
    {
        cerr << e.what() << endl;
        return -1;
    }
    //...
    return 0;
}

Las excepciones en C++ son similares a las de lenguajes como C# y Java. En el bloque , si se produce una excepción, lo detecta el primer bloque asociado cuyo tipo coincide con try el de la catch excepción. En otras palabras, la ejecución salta de la throw instrucción a la instrucción catch . Si no se encuentra ningún bloque catch utilizable, std::terminate se invoca y el programa se cierra. En C++, se puede producir cualquier tipo; sin embargo, se recomienda iniciar un tipo que derive directa o indirectamente de std::exception . En el ejemplo anterior, el tipo de excepción, invalid_argument , se define en la biblioteca estándar del archivo de <stdexcept> encabezado. C++ no proporciona ni requiere un bloque para asegurarse de que todos los recursos se finally liberan si se produce una excepción. La adquisición de recursos es la expresión de inicialización (RAII), que usa punteros inteligentes, proporciona la funcionalidad necesaria para la limpieza de recursos. Para obtener más información, vea Cómo: Diseñar para la seguridad de excepciones. Para obtener información sobre el mecanismo de desenredo de pila de C++, vea Excepciones y desenredo de pila.

Directrices básicas

Un control sólido de errores es complicado en cualquier lenguaje de programación. Aunque las excepciones proporcionan varias características que admiten un buen control de errores, no pueden realizar todo el trabajo por usted. Para comprender las ventajas del mecanismo de excepción, tenga en cuenta las excepciones al diseñar el código.

  • Use aserciones para comprobar si hay errores que nunca deberían producirse. Use excepciones para comprobar los errores que pueden producirse, por ejemplo, errores en la validación de entrada en parámetros de funciones públicas. Para obtener más información, vea la sección Excepciones frente a aserciones.

  • Use excepciones cuando el código que controla el error esté separado del código que detecta el error mediante una o varias llamadas a funciones que intervienen. Considere si usar códigos de error en su lugar en bucles críticos para el rendimiento, cuando el código que controla el error está estrechamente acoplado al código que lo detecta.

  • Para cada función que pueda producir o propagar una excepción, proporcione una de las tres garantías de excepción: la garantía segura, la garantía básica o la garantía nothrow (noexcept). Para obtener más información, vea Cómo: Diseñar para la seguridad de excepciones.

  • Produce excepciones por valor y las captura por referencia. No se detecta lo que no se puede controlar.

  • No use especificaciones de excepción, que están en desuso en C++11. Para más información, consulte la sección Especificaciones y noexcept excepciones.

  • Use los tipos de excepción de biblioteca estándar cuando se aplican. Derive los tipos de excepción personalizados de la exception jerarquía de clases.

  • No permita que las excepciones escapen de destructores o funciones de desasignación de memoria.

Excepciones y rendimiento

El mecanismo de excepción tiene un costo de rendimiento mínimo si no se produce ninguna excepción. Si se produce una excepción, el costo del recorrido y el desenredo de la pila es aproximadamente comparable al costo de una llamada de función. Se requieren estructuras de datos adicionales para realizar el seguimiento de la pila de llamadas después de especificar un bloque, y se requieren instrucciones adicionales para desenredar la pila si se try produce una excepción. Sin embargo, en la mayoría de los escenarios, el costo en el rendimiento y la superficie de memoria no es significativo. Es probable que el efecto adverso de las excepciones en el rendimiento sea significativo solo en los sistemas con restricción de memoria. O bien, en bucles críticos para el rendimiento, donde es probable que se produzca un error con regularidad y haya un acoplamiento estricto entre el código para controlarlo y el código que lo notifica. En cualquier caso, es imposible conocer el costo real de las excepciones sin generar perfiles y medir. Incluso en aquellos casos excepcionales en los que el costo es significativo, puede sopesar el aumento de la corrección, la facilidad de mantenimiento y otras ventajas que proporciona una directiva de excepciones bien diseñada.

Excepciones frente a aserciones

Las excepciones y aserciones son dos mecanismos distintos para detectar errores en tiempo de ejecución en un programa. Use instrucciones para probar las condiciones durante el desarrollo que assert nunca deben ser verdaderas si todo el código es correcto. No tiene sentido controlar este tipo de error mediante una excepción, porque el error indica que se debe solucionar algo en el código. No representa una condición de la que el programa tiene que recuperarse en tiempo de ejecución. detiene assert la ejecución en la instrucción para que pueda inspeccionar el estado del programa en el depurador. Una excepción continúa la ejecución desde el primer controlador catch adecuado. Use excepciones para comprobar las condiciones de error que pueden producirse en tiempo de ejecución incluso si el código es correcto, por ejemplo, "archivo no encontrado" o "falta de memoria". Las excepciones pueden controlar estas condiciones, incluso si la recuperación simplemente envía un mensaje a un registro y finaliza el programa. Compruebe siempre los argumentos de las funciones públicas mediante excepciones. Incluso si la función no tiene errores, es posible que no tenga control total sobre los argumentos que un usuario podría pasar a ella.

Excepciones de C++ frente a Windows de SEH

Tanto los programas de C como los de C++ pueden usar el mecanismo de control de excepciones estructurado (SEH) en el Windows operativo. Los conceptos de SEH son similares a los de las excepciones de C++, salvo que SEH usa las construcciones , y __try en lugar de y __except __finally try catch . En el compilador de Microsoft C++ (MSVC), se implementan excepciones de C++ para SEH. Sin embargo, al escribir código de C++, use la sintaxis de excepción de C++.

Para obtener más información sobre SEH, vea Control estructurado de excepciones (C/C++).

Especificaciones de excepciones y noexcept

Las especificaciones de excepción se introdujeron en C++ como una manera de especificar las excepciones que una función podría producir. Sin embargo, las especificaciones de excepción resultaron problemáticas en la práctica y están en desuso en el estándar de borrador de C++11. Se recomienda no usar especificaciones de excepción excepto para , que indica que la función no permite que se escape throw throw() ninguna excepción. Si debe usar especificaciones de excepción con el formato en desuso throw( type-name ) , MSVC compatibilidad es limitada. Para obtener más información, vea Especificaciones de excepción (throw). El noexcept especificador se presenta en C++11 como la alternativa preferida a throw() .

Consulte también

Cómo: Interfaz entre código excepcional y no excepcional
Referencia del lenguaje C++
Biblioteca estándar de C++