[C++]——Manejo de excepciones

Prefacio:

  • En este número, le explicaré los conocimientos relevantes sobre el manejo de excepciones  .

Tabla de contenido

(1) La forma tradicional de manejar errores en lenguaje C

(2) Concepto de excepción de C ++

(3) Uso anormal

1. Lanzamiento y recepción de excepción

1️⃣ Principios de combinación y lanzamiento de excepciones 

2️⃣ Principio de coincidencia y expansión de la pila de excepciones en la cadena de llamadas de funciones

2. Relanzamiento de excepción 

3. Seguridad anormal

4. Especificación de excepción

(4) Sistema de excepción de la biblioteca estándar de C++

(5) Ventajas y desventajas anormales

Resumir


(1) La forma tradicional de manejar errores en lenguaje C

Primero, revisemos las formas relevantes de manejar excepciones en lenguaje C:

  •  Programa de terminación , como afirmar, defecto: difícil de aceptar para los usuarios. Si ocurre un error de memoria, el error de división por cero finalizará el programa.

Aquí hay una descripción de código simple sobre el uso de macros para el manejo de excepciones:

#include <stdio.h>
#include <assert.h>


int divide(int num1, int num2) 
{
    assert(num2 != 0);  // 断言num2不等于0

    return num1 / num2;
}

int main() 
{
    int result = divide(10, 0);
    // 如果编译时定义了NDEBUG宏,assert会被禁用,否则会触发异常并终止程序执行

    return 0;
}

【explicar】

1. En el código anterior, la función se utiliza para implementar la operación de división de dos números enteros. Al utilizar macros dentro de funciones, puede realizar juicios condicionales para garantizar que el divisor no sea cero. Si es cero, se activará una excepción que finalizará la ejecución del programa.

2. En la función, llame a la función y pase el divisor como 0. Si la macro no está definida durante la compilación (es decir, el modo de depuración no está habilitado), se activa una excepción y se finaliza la ejecución del programa. Si se define una macro, se deshabilitará, no se activará ninguna excepción y el programa continuará ejecutando el código posterior.

[Visualización de salida]


  • Código de error de retorno , defecto: el programador necesita encontrar el error correspondiente por sí mismo. Por ejemplo, las funciones de interfaz de muchas bibliotecas del sistema expresan errores poniendo códigos de error en errno.

 El siguiente es un código de muestra que maneja excepciones devolviendo un código de error:

#include <stdio.h>
int divide(int num1, int num2, int* res)
{
    if (num2 == 0) 
    {
        return -1; // 返回错误码 -1 表示除数为零的异常情况
    }

    *res = num1 / num2;
    return 0; // 返回 0 表示成功
}

int main() 
{
    int num1 = 10, num2 = 0, res;
    int num = divide(num1, num2, &res);

    if (num != 0)
    {
        printf("Error: Divide by zero\n");
        // 处理错误的逻辑
    }
    else 
    {
        printf("Result: %d\n", res);
        // 处理正常情况的逻辑
    }

    return 0;
}

 【explicar】

  1. En la función, llame a la función y pase el divisor por 0. El código de error devuelto por la función se almacena en una variable y al juzgar el valor se puede determinar si se trata de una situación normal o anormal. Si no es igual a 0, significa que se ha producido una excepción y se puede realizar el manejo de errores de acuerdo con la situación específica.
  2. Si el código de error devuelto es 0, significa que la operación de división fue exitosa, el resultado del cálculo se puede obtener a través de la variable y se puede ejecutar la lógica de procesamiento normal correspondiente.

[Visualización de salida]

En la práctica, el lenguaje C básicamente utiliza el método de devolver códigos de error para manejar errores y, en algunos casos, el programa de terminación se usa para manejar errores muy graves.


(2) Concepto de excepción de C ++
 

En C++, la excepción es un mecanismo utilizado para manejar errores durante el tiempo de ejecución del programa. Las excepciones proporcionan una manera de salir del flujo normal del programa y pasar información de error al controlador apropiado para su procesamiento.

Los siguientes son algunos conceptos sobre las excepciones de C++:

  1. Lanzamiento de excepciones : cuando ocurre una excepción, puede usar la declaración de lanzamiento para lanzar la excepción. La declaración de lanzamiento generalmente contiene un objeto de excepción, que puede ser un tipo básico, un objeto de clase o un puntero;

  2. Captura de excepciones : después de que se lanza una excepción, el programa puede usar try-catchbloques de instrucciones para detectar y manejar la excepción. El bloque try contiene código donde pueden ocurrir excepciones y catchlos bloques se utilizan para detectar y manejar excepciones;

  3. Controlador de excepciones : un bloque catch es un bloque de código que se utiliza para manejar excepciones. En el bloque catch , la lógica de procesamiento correspondiente se puede ejecutar según el tipo de excepción lanzada. Puede haber varios  bloques catch , que coinciden con los tipos de excepción uno por uno en orden y ejecutan la lógica de procesamiento coincidente.

Si un bloque genera una excepción, el método para detectar la excepción utilizará las palabras clave try and catch. El código que puede generar
una excepción se coloca en el bloque de prueba, y el código en el bloque de prueba se denomina código protegido.

  • La sintaxis para usar declaraciones try/catch es la siguiente:
     
try
{
    // 保护的标识代码
}catch( ExceptionName e1 )
{
    // catch 块
}catch( ExceptionName e2 )
{
    // catch 块
}catch( ExceptionName eN )
{
    // catch 块
}

【resumen】

  1. Al utilizar adecuadamente el mecanismo de manejo de excepciones para capturar y procesar errores en el programa, se puede aumentar la solidez y la capacidad de mantenimiento del programa;
  2. El manejo apropiado de excepciones puede hacer que el código sea más claro y legible, y puede manejar mejor las excepciones y mejorar la tolerancia a fallas del programa.

(3) Uso anormal

1. Lanzamiento y recepción de excepción

En C++, los principios de coincidencia y lanzamiento de excepciones siguen los siguientes principios básicos:

1️⃣ Principios de combinación y lanzamiento de excepciones
 

  1. Excepción lanzada:

    • Cuando ocurre una excepción en el programa, puede usar throwdeclaraciones para generar la excepción.
    • throwLas declaraciones suelen contener un objeto de excepción, que puede ser un tipo primitivo, un objeto de clase o un puntero.
  2. Coincidencia de excepción:

    • La coincidencia de excepciones se refiere a seleccionar catchel bloque que maneja la excepción según el tipo de excepción lanzada.
    • El mecanismo de manejo de excepciones de C++ comparará los tipos de bloques tryen el bloque catchpara encontrar el bloque que pueda manejar el tipo de excepción catch.
  3. Relación de herencia y coincidencia de tipos de excepción:

    • C++ permite que los tipos de excepción formen una relación de herencia, es decir, los objetos de excepción de las clases derivadas pueden ser catchcapturados por bloques de clases base.
    • Si existe una relación de herencia para el tipo de excepción, el bloque de la clase derivada catchdebe colocarse catchantes del bloque de la clase base; de ​​lo contrario, el bloque de la clase derivada catchno se ejecutará.
  4. Mejor manejo de excepciones coincidentes:

    • El mecanismo de manejo de excepciones de C++ seleccionará el catchbloque más coincidente para manejar la excepción lanzada.
    • El bloque que mejor coincide es el bloque catchque es capaz de manejar el tipo de excepción lanzada o su tipo de clase base , es decir, la coincidencia más cercana para el tipo de excepción.catch
  5. Manejo de excepciones que no coinciden:

    • Si tryse genera una excepción dentro de un bloque y no catchse encuentra ningún bloque coincidente para manejar la excepción, la excepción se pasa más arriba en la pila de llamadas.
    • Si el bloque coincidente nunca maneja la excepción catch, el programa eventualmente terminará la ejecución y puede generar un mensaje de excepción.

【Precauciones】

  1. catchEl principio de lanzamiento y coincidencia de excepciones es elegir manejar las excepciones haciendo coincidir los bloques en orden, así catchque tenga cuidado con la disposición del orden de los bloques;
  2. Generalmente, debe comenzar con el tipo de excepción específico y luego hacer coincidir el tipo de clase base para garantizar que la excepción se pueda manejar correctamente y ejecutar la lógica de manejo de excepciones correspondiente.

2️⃣ Principio de coincidencia y expansión de la pila de excepciones en la cadena de llamadas de funciones

En la cadena de llamadas de funciones, el principio de coincidencia de expansión de la pila de excepciones especifica principalmente cómo hacer coincidir el tipo de excepción y seleccionar el código de manejo de excepciones correcto. Cuando ocurre una excepción, el sistema de ejecución de C++ verifica las llamadas a funciones en la pila de llamadas, comenzando con la función que se está ejecutando actualmente , para encontrar un bloque que coincida con el tipo de excepción lanzada catch.

Los siguientes son los principios para la expansión y coincidencia de la pila de excepciones:

  • Verifique el bloque de la función actual try:

    • Si la función actual contiene trybloques, el sistema de ejecución busca catchbloques coincidentes.
  • catchVerifique el bloque de la función actual :

    • Si la función actual contiene un bloque que coincide con el tipo de excepción lanzada catch, entonces ese catchbloque se ejecutará.
    • catchSi se encuentran varios bloques coincidentes , catchse seleccionará el bloque más cercano (el más cercano) para manejar la excepción.
  • Si la función actual no tiene ningún catchbloque coincidente :

    • La pila de excepciones se expande a la función de llamada de nivel superior.
    • Repita los pasos 1 y 2 hasta que encuentre un catchbloque coincidente o llegue a la parte superior de la pila de llamadas.
  • Si no se encuentra ningún catchbloque coincidente en toda la pila de llamadas :

    • La ejecución del programa finaliza y se llama a una función de biblioteca estándar terminate()para finalizar el programa.

 Notas importantes sobre el desenredado y la coincidencia de la pila de excepciones:

  • Las excepciones coinciden en el orden en que se expande la pila, no en el orden en que se generan las excepciones.
  • Un objeto de excepción de una clase derivada puede ser capturado por un bloque de la clase base , por lo que el bloque de la clase base debe colocarse antes del bloque catchde la clase derivada .catchcatch
  • Si se produce una excepción en una función sin un catchbloque coincidente, la excepción se propagará hacia la pila de llamadas hasta que catchse encuentre un bloque coincidente o se finalice el programa.
  • El desenrollado de la pila de excepciones cruzará los límites de funciones y subprocesos, por lo que estos principios de coincidencia también se aplican en programas de subprocesos múltiples.

 

Por ejemplo el siguiente ejemplo:

 

A continuación, entendámoslo en detalle a través del código:

double Division(int a, int b) 
{
    if (b == 0)
        throw "Division by zero condition!";
    else
        return ((double)a / (double)b);
}

void Func() 
{
    int len, time;
   cin >> len >> time;
   cout << Division(len, time) << endl;
}

int main() 
{
    try {
        Func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "unknown exception" << endl;
    }

    return 0;
}

Pantalla de salida:

 

 【explicar】

  1. Utilice el mecanismo de manejo de excepciones para detectar y manejar posibles excepciones de división por cero. catch (const char* errmsg)Cuando se produce la división por cero, el bloque lanza y detecta una excepción constante de cadena;
  2. Si ocurren otros tipos de excepciones, el bloque las catch (...)detecta y ejecuta la lógica de procesamiento correspondiente.

 

2. Relanzamiento de excepción
 

En C++, el reprocesamiento de excepciones permite que una catchexcepción detectada se maneje dentro de un bloque y se vuelva a generar, lo que permite que el código de manejo de excepciones de nivel superior maneje aún más la excepción. throwLas excepciones se pueden volver a lanzar mediante declaraciones.

A continuación se muestra un código de muestra que utiliza el lanzamiento de excepciones:

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}
void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw;
	}
	// ...
	cout << "delete []" << array << endl;
	delete[] array;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

【explicar】

  1. En Funcla función, si se produce una excepción de división por cero, se detectará la excepción y arrayse generará la información eliminada. Luego, arrayse libera (se usa delete[]) y throwla excepción se vuelve a generar usando la declaración. De esta manera, la excepción se pasa al código de nivel superior.
  2. En mainla función, la excepción es capturada por el catchbloque más externo y se genera la información de la excepción.
  3. Al Funcvolver a generar la excepción en la función y liberarla antes y después de detectar la excepción array, puede asegurarse de que los recursos relacionados se hayan liberado antes de que la excepción pase a niveles superiores.

【resumen】

Al volver a generar excepciones, la excepción se puede manejar adecuadamente donde se detecta, y se puede continuar manejando la misma excepción u otras operaciones realizadas en código de nivel superior. Este mecanismo proporciona flexibilidad y propagación ascendente de errores. 


3. Seguridad anormal

  • El constructor completa la construcción y la inicialización del objeto . Es mejor no lanzar una excepción en el constructor , de lo contrario el objeto puede estar incompleto o no inicializado por completo.
  • El destructor completa principalmente la limpieza de recursos . Es mejor no lanzar excepciones en el destructor, de lo contrario puede causar pérdidas de recursos (pérdidas de memoria, identificadores no cerrados, etc.)
  • Las excepciones en C++ a menudo conducen a pérdidas de recursos. Por ejemplo, las excepciones se lanzan en nuevos y eliminados, lo que resulta en pérdidas de memoria, y las excepciones se lanzan entre bloqueo y desbloqueo, lo que resulta en un punto muerto. C++ a menudo usa RAII para resolver los problemas anteriores. Acerca de RAII Explicaremos en esta sección los punteros inteligentes.

4. Especificación de excepción

 En C++, una especificación de excepción es una forma de especificar en una declaración de función las excepciones que puede generar una función. Se puede incluir una especificación de excepción como parte de una función para identificar los tipos de excepciones que la función puede generar. Específicamente, una especificación de excepción especifica una lista de tipos de excepción que una función puede generar.

En C++98\03, las especificaciones de excepción utilizan throw()declaraciones. Por ejemplo:

void foo() throw(int, std::exception);

【explicar】

  1. El código anterior indica que la función foopuede generar una excepción de inttipo y exceptiontipo;
  2. Si la función genera otro tipo de excepción que no figura en la especificación de excepción, el programa llama unexpecteda la función, lo que de forma predeterminada hace que terminatefinalice el programa llamado.

A continuación se muestran más ejemplos: 

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);

// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);

// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();

En C++ 11, se introdujo un mecanismo de manejo de excepciones más flexible y seguro, que es una alternativa a las especificaciones de excepción: descripciones de excepciones . La descripción de la excepción utiliza noexceptpalabras clave para especificar si la función puede generar excepciones.

Las funciones que utilizan noexceptla palabra clave se pueden denominar " noexceptfunciones" o "funciones de excepción que no lanzan". Tienen algunos usos y ventajas importantes en:

  1. Optimización del rendimiento : el compilador puede noexceptrealizar algunas optimizaciones basadas en promesas explícitas de;
  2. Propagación de excepciones : ayuda a evitar que las excepciones se propaguen a contextos donde no se deben manejar excepciones;

A continuación se muestran algunos noexceptejemplos de uso:

void myFunction() noexcept {
  // 函数体,不会抛出异常
}

void anotherFunction() {
  // 函数体,可能会抛出异常
}

void myFunction2() noexcept(true) {
  // 与上面的 myFunction 等效,不会抛出异常
}

void myFunction3() noexcept(false) {
  // 与 anotherFunction 等效,可能会抛出异常
}

//不会抛出异常
thread (thread&& x) noexcept;

【Precauciones】

  1. En C++ 11, noexceptlas palabras clave se pueden usar como parte del tipo de función para indicar si la función genera una excepción;
  2. Después de C++ 17, noexceptse admiten expresiones de función para decidir dinámicamente si se lanza una excepción. Esto hace que las especificaciones de excepción sean más flexibles y dinámicas en algunas situaciones específicas.

(4) Sistema de excepción de la biblioteca estándar de C++

C++ proporciona una serie de excepciones estándar, definidas en , que podemos usar en nuestros programas. Están
organizados en una jerarquía de clases padre-hijo de la siguiente manera:
 

 

 

Nota : En la práctica, podemos heredar la clase de excepción para implementar nuestra propia clase de excepción. Pero en la práctica, muchas empresas definen su propio sistema de herencia de excepciones como el anterior. Porque el diseño de la biblioteca estándar de C++ no es fácil de usar.
 

int main()
{
	try {
		vector<int> v(10, 5);
		// 这里如果系统内存不够也会抛异常
		v.reserve(1000000000);
		// 这里越界会抛异常
		v.at(10) = 100;
	}
	catch (const exception& e) // 这里捕获父类对象就可以
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}

(5) Ventajas y desventajas anormales

Ventajas de las excepciones de C++:

  • 1. Una vez que se define el objeto de excepción, se puede mostrar de forma clara y precisa diversa información de error en comparación con el método del código de error, e incluso puede incluir información de llamada de pila , lo que puede ayudar a localizar mejor los errores del programa.
  • 2. Un gran problema con la forma tradicional de devolver códigos de error es que en la cadena de llamadas a funciones, si una función profunda devuelve un error, entonces tenemos que devolver el error capa por capa, y solo la capa más externa puede obtener el error. los detalles a continuación explique.
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,
ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因
为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;
	// 权限不足
	if (...)
		return 2;
}
int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0)
		return errno;
}
int main()
{
	if (ServerStart() < 0)
		...
		return 0;
}
  • 3. Muchas bibliotecas de terceros contienen excepciones, como boost, gtest, gmock y otras bibliotecas de uso común, por lo que debemos usar excepciones al usarlas.
  • 4. Es más fácil manejar algunas funciones usando excepciones, por ejemplo, el constructor no devuelve un valor, por lo que es inconveniente usar códigos de error. Por ejemplo, una función como el operador T&, si la pos está fuera de los límites, solo puede usar una excepción o finalizar el procesamiento del programa, y ​​no hay forma de indicar un error a través del valor de retorno.

Desventajas de las excepciones de C++:

  • 1. Las excepciones harán que el flujo de ejecución del programa salte violentamente, y es muy caótico, y saltará aleatoriamente cuando se produzca un error durante el tiempo de ejecución. Esto nos dificultará el seguimiento, la depuración y el análisis del programa.
  • 2. Las excepciones tienen cierta sobrecarga de rendimiento. Por supuesto, con la rápida velocidad del hardware moderno, este impacto es básicamente insignificante.
  • 3. C ++ no tiene un mecanismo de recolección de basura y los recursos deben administrarse ellos mismos. Salvo excepciones, es muy fácil provocar problemas de seguridad anormales, como pérdidas de memoria y bloqueos. Esto requiere el uso de RAII para manejar problemas de gestión de recursos. El costo del aprendizaje es mayor.
  • 4. El sistema de excepciones de la biblioteca estándar de C++ no está bien definido, lo que hace que todos definan su propio sistema de excepciones, lo cual es muy confuso.
  • 5. Utilice excepciones lo más estandarizadas posible, de lo contrario las consecuencias serán desastrosas. Si se lanzan excepciones a voluntad, los usuarios capturados por la capa externa se sentirán miserables. Entonces hay dos puntos en la especificación de excepción: 1. Los tipos de excepción lanzados se heredan de una clase base. 2. Si una función arroja una excepción y qué tipo de excepción arroja se estandarizan usando func() throw();.

Resumir

Lo anterior es todo el conocimiento sobre excepciones en c ++ 11. A continuación, ¡revise brevemente este artículo! ! !

  1. En términos generales, las ventajas de las excepciones superan las desventajas, por lo que seguimos fomentando el uso de excepciones en ingeniería;
  2. Además, los lenguajes OO básicamente utilizan excepciones para manejar errores, lo que también puede verse como una tendencia general.
     

Eso es todo por este artículo. ¡Gracias a todos por mirar y apoyar! ! !

Supongo que te gusta

Origin blog.csdn.net/m0_56069910/article/details/132540415
Recomendado
Clasificación