Compartir a través de


Controlar errores y excepciones (C++ moderno)

En C++ moderno, en la mayoría de los escenarios, la manera preferida de notificar y de controlar errores lógicos y errores de tiempo de ejecución es utilizar excepciones. Esto 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 saber cómo manejarlo. Las excepciones ofrecen una manera formal y bien definida para el código que detecta errores para pasar la información hasta la pila de llamadas.

Los errores de programa se dividen normalmente en dos categorías: errores lógicos que se producen por errores de programación, por ejemplo, “índice fuera un error de intervalo” y errores de tiempo de ejecución que van más allá del control del programador, por ejemplo, un error de “servicio de red no disponible”. En programación de estilo C y en COM, el informe de errores se administra devolviendo un valor que representa un código de error o un código de estado de una función determinada, o estableciendo una variable global que el llamador pueda recuperar opcionalmente después de cada llamada de función para ver si se notificaron errores. Por ejemplo, la programación COM utiliza el valor devuelto de HRESULT para comunicar errores al llamador, y la API Win32 tiene la función de GetLastError para recuperar el último error detectado por la pila de llamadas. En ambos casos, es responsabilidad del llamador reconocer el código y responder al mismo según corresponda. Si el llamador no controla el código de error, el programa podría bloquearse sin advertencia, o continuar ejecutándose con datos no válidos y generar resultados incorrectos.

Las excepciones se prefieren en C++ moderno por las razones siguientes:

  • Una excepción fuerza el código de llamada para reconocer una condición de error y para controlarla. Las excepciones no controladas detienen la ejecución del programa.

  • Una excepción omite el punto en la pila de llamadas que puede controlar el error. Las funciones intermedias pueden dejar la propagación de excepciones. No tienen que coordinar con otros niveles.

  • El mecanismo de "desenredo" de la pila de excepción destruye todos los objetos en el ámbito según reglas bien definidas después de que se produzca una excepción.

  • 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;
class MyClass
{
public:
   void MyFunc(char 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++ se parecen a las de lenguajes como C# y Java. En el bloque de try, si se produce una excepción, será detectada por el primer bloque catch asociado cuyo tipo coincide con esa excepción. Es decir, la ejecución salta de la instrucción de throw a la instrucción de catch. Si no se encuentra ningún bloque catch utilizable, se invoca std::terminate y el programa se cierra. En C++, cualquier tipo pueden ser lanzado; sin embargo, recomendamos que lance un tipo que se 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 encabezado <stdexcept>. C++ no proporciona, y no requiere, un bloque finally para asegurarse de que se liberen todos los recursos si se produce una excepción. La adquisición de recurso es la expresión de inicialización (RAII), que utiliza 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 las excepciones. Para obtener información sobre el mecanismo de "desenredo" de la pila de C++, vea Excepciones y desenredo de pila en C++.

Instrucciones básicas

El control de errores sólido es importante en cualquier lenguaje de programación. Aunque las excepciones proporcionan varias características que admiten un control de errores, no pueden hacer todo el trabajo. Para reconocer las ventajas del mecanismo de excepción, mantenga las excepciones en cuenta al diseñar el código.

  • Utilice aserciones para comprobar los errores que no deben aparecer nunca. Utilice excepciones para comprobar si se podrían producir errores, 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 titulada Excepciones frente a aserciones.

  • Utilice excepciones cuando el código que controla el error se podría separar de código que detecta el error por una o más llamadas de función intermedias. Considere si utilizar códigos de error en lugar de bucles críticos para el rendimiento cuando el código que controla el error está estrechamente acoplado al código que lo detecta. Para obtener más información sobre cuándo no se han de utilizar excepciones, vea When Not to Use Exceptions.

  • Para cada función que pudiera 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 las excepciones.

  • Las excepciones de captura por valor, las detectan por referencia. No detecte lo que no puede administrar. Para obtener más información, vea Instrucciones para Throwing y Catching Excepciones (C++).

  • No utilice especificaciones de excepciones que están desusadas en C++11. Para obtener más información, vea la sección titulada Especificaciones de excepciones y noexcept.

  • Utilice los tipos de excepción estándar de biblioteca cuando se aplican. Derive tipos de excepción personalizados de la jerarquía de la clase de excepción. Para obtener más información, vea Cómo: Utilice objetos de excepción estándar de biblioteca.

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

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 desenredo de la pila es aproximadamente comparable al costo de una llamada de función. Las estructuras de datos adicionales deben seguir la pila de llamadas después de que se escriba un bloque try, y se requieren instrucciones adicionales para desenredo de la pila si se produce una excepción. Sin embargo, en la mayoría de los escenarios, el costo de rendimiento y el consumo de memoria no son significativos. El efecto adverso de excepciones en rendimiento es probable que tenga sentido solo en sistemas muy restringidos de memoria, o en bucles críticos para el rendimiento donde es probable que se produzca un error periódicamente y el código para controlarlo está estrechamente acoplado al código que notifica sobre el mismo. En cualquier caso, no se puede conocer el costo real de excepciones sin generar perfiles y medir. Incluso en esos casos raros en los que el costo es significativo, puede sopesarlo frente a la mayor exactitud, mantenimiento más sencillo y otras ventajas proporcionadas por una directiva de excepciones bien diseñada.

Excepciones frente a aserciones

Las excepciones y aserciones son dos mecanismos diferentes para detectar los errores en tiempo de ejecución de un programa. Utilice aserciones para probar condiciones durante el desarrollo que nunca debe ser true si todo el código es correcto. No hay ningún punto de administrar el error mediante una excepción porque el error indica que es necesario corregir algo en el código y no representa una condición de la que el programa tiene que recuperar en tiempo de ejecución. Al validar se detiene la ejecución de la instrucción de modo que puede inspeccionar el estado del programa en el depurador; una excepción continúa la ejecución del primer controlador adecuado de la captura. Utilice excepciones para comprobar las condiciones de error que pueden producirse en tiempo de ejecución aunque el código sea correcto, por ejemplo, "archivo no encontrado" o "memoria insuficiente". Es posible que desee realizar la recuperación de estas condiciones, aunque la recuperación solo genera un mensaje para un registro y cierra el programa. Compruebe siempre los argumentos para publicar funciones mediante excepciones. Incluso si la función no tiene errores, podría no tener control total sobre los argumentos que un usuario puede pasarle.

Excepciones de C++ en comparación con las excepciones de Windows SEH

Los programas C y C++ pueden utilizar el mecanismo de control de excepciones estructurado (SEH) en el sistema operativo de Windows. Los conceptos en SEH se parecen a las de excepciones de C++, salvo que SEH utiliza las construcciones __try, __except y de __finally en lugar de try y de catch. En Visual C++, las excepciones de C++ se implementan para SEH. Sin embargo, al escribir código de C++, utilice la sintaxis de excepciones de C++.

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

Especificaciones de excepciones y noexcept

Las especificaciones de excepciones se incluyeron en C++ como una manera de especificar las excepciones que puede producir una función. Sin embargo, se demostró que las especificaciones de excepciones son problemáticas en la práctica y están en desuso en el estándar de borrador C++11. Recomendamos no utilizar especificaciones de excepciones, a excepción de throw(), que indica que la excepción no permite ninguna excepción como secuencia de escape. Si debe utilizar las especificaciones de excepción del tipo throw(type), tenga en cuenta que Visual C++ sale del estándar de determinadas maneras. Para obtener más información, vea Especificaciones de excepción. Se muestra el especificador noexcept en C++11 como la alternativa recomendada a throw().

Vea también

Conceptos

Cómo: Interfaz entre código excepcional y no excepcional

Otros recursos

Aquí está otra vez C++ (C++ moderno)

Referencia de lenguaje C++

Referencia de biblioteca estándar de C++