【C++】Excepción de C++

1. La forma tradicional de manejar errores en lenguaje C.

Existen dos mecanismos tradicionales de manejo de errores en lenguaje C:

  1. Terminar el programa , como afirmar.

    Desventajas: Difícil de aceptar para los usuarios. Si ocurre un error de memoria, el programa terminará con un error de división por cero.

  2. Código de error de retorno

    Desventaja: los programadores necesitan encontrar ellos mismos los errores correspondientes. Por ejemplo, las funciones de interfaz de muchas bibliotecas del sistema expresan errores poniendo códigos de error en errno.

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. El concepto de excepciones de C++.

Las excepciones son una forma de manejar errores . Cuando una función encuentra un error que no puede manejar, puede generar una excepción y dejar que la persona que llama directa o indirectamente a la función maneje el error .

  • lanzar: cuando ocurre un problema, el programa generará una excepción. Esto se hace usando la palabra clave throw.
  • catch: cuando desee manejar el problema, capture la excepción a través del controlador de excepciones. La palabra clave catch se usa para detectar excepciones y puede haber múltiples capturas para capturar.
  • try: el código en el bloque try identifica la excepción específica que se activará y generalmente va seguido de uno o más bloques catch.

Si un bloque genera una excepción, el método para detectar la excepción utiliza las palabras clave try y catch. El código que puede generar excepciones se coloca en el bloque try, que se denomina código de protección. La sintaxis para usar declaraciones try/catch es la siguiente:

try
{
    
    
    //保护的标识代码
}
catch(ExceptionName e1)
{
    
    
    //catch块
}
catch(ExceptionName e2)
{
    
    
    //catch块
}
catch(ExceptionName eN)
{
    
    
    //catch块
}

3. Uso anormal

3.1 Excepciones de lanzamiento y recepción

Principios de emparejamiento y lanzamiento de excepciones

  • Las excepciones se generan al lanzar objetos . El tipo de objeto determina qué código de manejo de capturas debe activarse.
  • El código de manejo seleccionado es el de la cadena de llamadas que coincide con el tipo de objeto y está más cerca de la ubicación donde se genera la excepción.
  • Después de que se lanza un objeto de excepción, se generará una copia del objeto de excepción. Debido a que el objeto de excepción lanzado puede ser un objeto temporal, se generará un objeto de copia. Este objeto temporal copiado se destruirá después de ser capturado. (El procesamiento aquí es similar a la función de retorno por valor)
  • catch(…) puede detectar cualquier tipo de excepción, el problema es que no sabemos cuál es el error de excepción.
  • Existe una excepción al principio de lanzar y capturar excepciones. No todos los tipos coinciden exactamente. Los objetos de clases derivadas que se pueden lanzar se pueden capturar usando la clase base . Esto es muy práctico en la práctica.

Principio de coincidencia de expansión de pila de excepciones en la cadena de llamadas de funciones

  • Primero verifique si throw está dentro del bloque try y, de ser así, busque una declaración catch coincidente . Si hay una coincidencia, llame al lugar de captura para su procesamiento.
  • Si no se encuentra ninguna captura, salga de la pila de funciones actual y continúe buscando capturas coincidentes en la pila de la función de llamada de la capa superior.
  • Si se alcanza la pila de la función principal y aún no hay coincidencia, el programa finalizará . El proceso anterior de encontrar cláusulas catch coincidentes a lo largo de la cadena de llamadas se llama desenrollado de pila. Entonces, en la práctica, tenemos que agregar un catch (...) al final para detectar cualquier tipo de excepción, de lo contrario, si hay una excepción que no se detecta, el programa terminará directamente.
  • Una vez encontrada y procesada la cláusula catch coincidente, la ejecución continuará siguiendo la cláusula catch.

imagen-20230811010149061

double Division(int a, int b)
{
    
    
	// 当b == 0时抛出异常
	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;
}
void Test1()
{
    
    
	try 
	{
    
    
		Func();
	}
	catch (const char* errmsg)
	{
    
    
		cout << errmsg << endl;
	}
	catch (...) 
	{
    
    
		cout << "unkown exception" << endl;
	}
}

imagen-20230811010453845

3.2 Relanzamiento de excepciones

Es posible que un solo catch no pueda manejar completamente una excepción. Después de un procesamiento de corrección, se espera que se entregue a una función de la cadena de llamadas externa para su procesamiento. El catch puede pasar la excepción a una función de nivel superior para su procesamiento. volviéndolo a lanzar.

Veamos el siguiente fragmento de código:

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

3.3 Excepcionalmente seguro

Las excepciones de C++ todavía tienen algunos problemas de seguridad

  • El constructor completa la construcción e inicialización del objeto. Es mejor no generar excepciones en el constructor, de lo contrario puede provocar una construcción o inicialización incompleta del objeto.
  • El destructor completa principalmente la operación de limpieza de recursos, es mejor no lanzar excepciones en el destructor, de lo contrario puede provocar pérdidas de recursos (pérdidas de memoria o identificadores no cerrados, etc.).
  • Las excepciones en C++ a menudo conducen a pérdidas de recursos. Por ejemplo, se lanzan excepciones en nuevos y eliminados, lo que provoca pérdidas de memoria, y se lanzan excepciones entre bloqueo y desbloqueo, lo que provoca un punto muerto. C++ a menudo usa RAII para resolver los problemas anteriores.

3.4 Especificaciones de excepción

Dado que el uso irregular de excepciones traerá muchas consecuencias muy graves, C ++ 98 introdujo la especificación de excepción. La especificación de excepción recomienda a los programadores describir la interfaz de excepción para cada función . El propósito es que los usuarios de la función sepan que la función puede ¿Qué son las excepciones? lanzados son los siguientes:

  1. Siga la función con throw(type) para enumerar todos los tipos de excepción que esta función puede generar.
  2. La función va seguida de throw(), lo que indica que la función no genera una excepción.
  3. Si no hay una declaración de interfaz de excepción, esta función puede generar cualquier tipo de excepción
//这里表示func可以抛出A,B,C,D四种类型的异常
void func() 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();

Sin embargo, debido a que la interfaz de excepción de función de C++98 es solo un enfoque sugerido, no un requisito gramática obligatorio, y debido a que es problemático escribir todas las excepciones que una función puede generar, las especificaciones de excepción de C++98 son casi inútiles en el desarrollo real, si nadie lo sigue, es inútil;

Para permitir que las personas describan interfaces de excepción para funciones, C++ 11 simplifica la descripción de la interfaz de excepción :

  • Agregar palabras clave después de la función noexceptsignifica que la función no generará ninguna excepción.
  • Si no se agrega ninguna palabra clave después de la función, significa que se puede generar cualquier tipo de excepción.
// C++11 中新增的 noexcept,表示不会抛异常
thread() noexcept;
thread(thread&& x) noexcept;

Y C++ 11 también verifica las funciones modificadas con noexcept. Si la función se modifica con noexcept pero puede generar una excepción, el compilador informará una advertencia, pero no afectará la corrección del programa:

int at(vector<int>& v, size_t pos) noexcept
{
    
    
	if (pos >= v.size())
		throw out_of_range("数组越界");
	else
		return v[pos];
}

void Test1()
{
    
    
	vector<int> v = {
    
    1, 2, 3, 4, 5};
	try {
    
    
		at(v, 5);
	}
	catch (out_of_range& errmsg)
	{
    
    
		cout << errmsg.what() << endl;
	}
	catch (...) {
    
    
		cout << "未知异常" << endl;
	}
}

Por favor agregue la descripción de la imagen.

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

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

imagen

Por favor agregue la descripción de la imagen.

Entre los errores más comunes se encuentran: bad_alloc: esta excepción se produce cuando falla el nuevo espacio; runtime_error: error de tiempo de ejecución, como error de división por 0, etc.; error fuera de límites de matriz out_of_range.

Uso del sistema de excepciones de la biblioteca estándar C++:

void Test2()
{
    
    
	vector<int> v(10);
	try {
    
    
		v.reserve(100000000);
		v.at(20) = 5;
	}
	catch (exception& errmsg)
	{
    
    
		cout << errmsg.what() << endl;
	}
	catch (...)
	{
    
    
		cout << "unknow Exception" << endl;
	}
}

Por favor agregue la descripción de la imagen.

Aunque podemos usar directamente las excepciones proporcionadas por el estándar C++, o podemos heredar la clase de excepción para implementar nuestra propia clase de excepción, en el desarrollo real muchas empresas definirán un sistema de herencia de excepciones separado porque la biblioteca estándar de C++ no está lo suficientemente diseñada. usar. Además, normalmente no utilizamos excepciones al escribir nuestro propio código, por lo que podemos comprender el contenido de las excepciones estándar de C++.

5. Sistema de excepción personalizado

De hecho, muchas empresas estandarizarán la gestión de sus propios sistemas de excepción definidos, porque si en un proyecto todos lanzan excepciones a voluntad, entonces la persona que llama más externa no podrá jugar, por lo que en la práctica definirán un sistema de excepción. Sistema de especificación de herencia: defina la clase base más básica. Los objetos de excepción lanzados por todos son objetos de clase derivada que heredan la clase de excepción. Por lo tanto, la sintaxis de excepción puede usar la clase base para capturar los objetos de clase derivada arrojados, por lo que el final El externo El valor solo necesita capturar la clase base .

imagen

Echemos un vistazo al sistema de herencia de excepciones comúnmente utilizado en el desarrollo de servidores.

class Exception//这里定义了一个异常的基类,后面对于不同类型的异常,将进行不同的继承
{
    
    
public:
    Exception(const string& errmsg, int id)
        :_errmsg(errmsg)
        ,_id(id)
    {
    
    }
    virtual string what() const//这里的what使用virtual修饰,以达到多态使用,返回不同的错误信息
    {
    
    
        return _errmsg;
    }
protected:
    string _errmsg;
    int _id;
};
class SqlException : public Exception//sql类型的异常
{
    
    
public:
    SqlException(const string& errmsg, int id, const string& sql)
        :Exception(errmsg, id)
        , _sql(sql)
    {
    
    }
    virtual string what() const//对虚函数进行重写,实现多态
    {
    
    
        //修改异常信息并返回
        string str = "SqlException:";
        str += _errmsg;
        str += "->";
        str += _sql;
        return str;
    }
private:
    const string _sql;//保存出现错误的sql语句
};
class CacheException : public Exception//Cache类型异常
{
    
    
public:
    CacheException(const string& errmsg, int id)
        :Exception(errmsg, id)
    {
    
    }
    virtual string what() const//虚函数重写
    {
    
    
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};
class HttpServerException : public Exception//网络类型异常
{
    
    
public:
    HttpServerException(const string& errmsg, int id, const string& type)
        :Exception(errmsg, id)
        , _type(type)
    {
    
    }
    virtual string what() const
    {
    
    
        string str = "HttpServerException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }
private:
    const string _type;
};
void SQLMgr()
{
    
    
    srand(time(nullptr));
    if (rand() % 4 == 0)
    {
    
    
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }
    //throw "xxxxxx";
}
void CacheMgr()
{
    
    
    srand(time(0));
    if (rand() % 5 == 0)
    {
    
    
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
    
    
        throw CacheException("数据不存在", 101);
    }
    SQLMgr();//如果没有抛出异常就调用SQL层操作
}
void HttpServer()
{
    
    
    // ...
    srand(time(0));
    if (rand() % 3 == 0)
    {
    
    
        throw HttpServerException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
    
    
        throw HttpServerException("权限不足", 101, "post");
    }
    CacheMgr();//如果网络服务没有问题,在这里调用内存操作
}
void Test3()
{
    
    
    while(1)
    {
    
    
        this_thread::sleep_for(chrono::seconds(1));
        try{
    
    
            HttpServer();//这里调用网络服务
        }
        catch (const Exception& e) // 这里捕获父类对象就可以
        {
    
    
            // 多态
            cout << e.what() << endl;
        }
        catch (...)
        {
    
    
            cout << "Unkown Exception" << endl;
        }
    }
}

Por favor agregue la descripción de la imagen.

6. Ventajas y desventajas de las excepciones

Ventajas de las excepciones de C++

  1. Una vez que se define el objeto de excepción, en comparación con el método del código de error, se puede mostrar de forma clara y precisa diversa información 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;
  3. Muchas bibliotecas de terceros contienen excepciones , como boost, gtest, gmock y otras bibliotecas de uso común, por lo que también debemos usar excepciones al usarlas;
  4. Algunas funciones son más fáciles de manejar usando excepciones, por ejemplo, el constructor no tiene valor de retorno, por lo que es inconveniente usar códigos de error para manejarlo. Por ejemplo, en T& operatoruna función como esta, si pos cruza el límite, solo puede usar excepciones o finalizar el programa, y ​​no hay forma de indicar un error mediante un valor de retorno ;

Desventajas de las excepciones de C++

  1. Las excepciones harán que el flujo de ejecución del programa salte y sea muy confuso, y saltará cuando se produzca un error durante el tiempo de ejecución. Esto nos hará más difícil rastrear, depurar y analizar el 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 usted mismo debe administrar los recursos . Las excepciones pueden provocar fácilmente 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. Intente utilizar excepciones de forma estandarizada , 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(); (o C++11 noexcept).

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


Fin de esta sección…

Supongo que te gusta

Origin blog.csdn.net/weixin_63249832/article/details/132242473
Recomendado
Clasificación