前言:
目录
(一)C语言传统的处理错误的方式
(二)C++异常概念
(三)异常的使用
1、异常的抛出和捕获
1️⃣ 异常的抛出和匹配原则
2️⃣ 在函数调用链中异常栈展开匹配原则
2、异常的重新抛出
3、异常安全
4、异常规范
(四)C++标准库的异常体系
(五)异常的优缺点
总结
首先,我们回顾一下C语言处理异常的相关方式:
下面是使用宏进行异常处理的简单代码描述:
#include
#include
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;
}
【解释说明】
1、在上述代码中,函数用于实现两个整数的除法运算。通过在函数内使用宏,可以进行条件判断,确保除数不为零。如果为零,会触发异常,终止程序的执行。
2、在函数中,调用函数并传递除数为0的情况。如果编译时未定义宏(即未启用调试模式),会触发异常并终止程序执行。如果定义了宏,则会被禁用,不会触发异常,程序会继续执行后续的代码。
【输出展示】
下面是使用返回错误码的方式处理异常的示例代码:
#include
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;
}
【解释说明】
【输出展示】
实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。
在C++中,异常(Exception)是一种用于处理程序运行时错误的机制。异常提供了一种跳出正常程序流程的方式,将错误信息传递到适当的处理程序进行处理。
以下是关于C++异常的一些概念:
异常抛出:当发生异常情况时,可以使用throw 语句将异常抛出。throw 语句通常包含一个异常对象,该对象可以是基本类型、类对象或指针;
异常捕获:异常被抛出后,程序可以使用try-catch
语句块来捕获并处理异常。try 块包含可能发生异常的代码,而catch
块用于捕获和处理异常;
异常处理程序:catch 块是用于处理异常的代码块。在catch 块中,可以根据抛出的异常类型来执行相应的处理逻辑。可以有多个 catch 块,按照顺序逐个匹配异常类型并执行匹配的处理逻辑。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛
出异常的代码,try 块中的代码被称为保护代码。
try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
【小结】
在C++中,异常的抛出和匹配原则遵循以下几个基本原则:
异常抛出:
throw
语句将异常抛出。throw
语句通常包含一个异常对象,该对象可以是基本类型、类对象或指针。异常匹配:
catch
块。try
块中catch
块的类型,找到能处理该异常类型的catch
块。异常类型匹配和继承关系:
catch
块捕获。catch
块应该放在基类的catch
块之前;否则,派生类的catch
块将无法执行。最匹配的异常处理:
catch
块来处理抛出的异常。catch
块是指能够处理抛出的异常类型或其基类类型的catch
块,即异常类型匹配的最接近情况。异常未匹配的处理:
try
块中抛出了异常,但没有找到匹配的catch
块处理该异常,异常将传递到更高层的调用栈。catch
块处理,最终导致程序终止执行,并可能输出异常信息。【注意事项】
catch
块来选择处理异常,因此在catch
块的顺序布置上要谨慎;在函数调用链中,异常栈展开匹配原则主要指定了如何匹配异常类型并选择正确的异常处理代码。当异常发生时,C++运行时系统会从当前执行的函数开始,逐级检查调用栈中的函数调用,以查找与抛出的异常类型匹配的
catch
块。
以下是异常栈展开和匹配的原则:
检查当前函数的try
块:
try
块,运行时系统将查找匹配的catch
块。检查当前函数的catch
块:
catch
块,那么该catch
块将被执行。catch
块,将选择最接近的(最近的)catch
块来处理异常。如果当前函数没有匹配的catch
块:
catch
块或达到调用栈的最顶层。如果在整个调用栈中没有找到匹配的catch
块:
terminate()
来终止程序。关于异常栈展开和匹配的重要注意事项:
catch
块捕获,因此在派生类的catch
块之前应放置基类的catch
块。catch
块处理,异常会一直沿着调用栈向上传递,直到找到匹配的catch
块或终止程序。
例如以下示例:
接下来通过代码来具体的理解:
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;
}
输出展示:
【解释说明】
catch (const char* errmsg)
块捕获;catch (...)
块捕获并执行相应的处理逻辑。
在C++中,异常的重新抛出允许在
catch
块内部对捕获的异常进行处理并将其重新抛出,以便让更高层的异常处理代码进一步处理该异常。可以使用throw
语句将异常重新抛出。
以下是一个使用异常重新抛出的示例代码:
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;
}
【解释说明】
Func
函数中,如果除以零的异常发生,异常将被捕获,并输出删除array
的信息。然后,array
会被释放(使用delete[]
),并使用throw
语句重新抛出异常。这样,异常会传递到更高层级的代码中。main
函数中,异常被最外层的catch
块捕获,并输出异常信息。Func
函数中重新抛出异常,并在捕获异常之前及其后释放array
,可以确保在异常传递给更高层级之前,已经释放了相关的资源。【小结】
通过异常的重新抛出,可以在异常被捕获的地方对异常进行适当处理,并在更高层级的代码中继续处理相同的异常或进行其他操作。这种机制提供了灵活性和错误的向上传递。
在C++中,异常规范是一种在函数声明中指定函数可能抛出的异常的方式。异常规范可以作为函数的一部分,用于标识函数可能引发的异常类型。具体来说,异常规范指定了函数可抛出的异常类型列表。
在C++98\03 中,异常规范使用了throw()
声明。例如:
void foo() throw(int, std::exception);
【解释说明】
foo
可能抛出int
类型和exception
类型的异常;unexpected
函数,默认情况下会导致terminate
被调用终止程序。更多示例如下图所示:
// 这里表示这个函数会抛出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();
在C++11开始引入了更为灵活和安全的异常处理机制,即异常规范的替代方案:异常说明。异常说明使用
noexcept
关键字指定函数是否允许抛出异常。
使用noexcept
关键字的函数可以被称为“noexcept
函数”或“不抛异常函数”。它们在以下方面有一些重要的用途和优点:
noexcept
的显式承诺做出一些优化;下面是一些使用noexcept
的示例:
void myFunction() noexcept {
// 函数体,不会抛出异常
}
void anotherFunction() {
// 函数体,可能会抛出异常
}
void myFunction2() noexcept(true) {
// 与上面的 myFunction 等效,不会抛出异常
}
void myFunction3() noexcept(false) {
// 与 anotherFunction 等效,可能会抛出异常
}
//不会抛出异常
thread (thread&& x) noexcept;
【注意事项】
noexcept
关键字可以作为函数类型的一部分,标志着函数是否抛出异常;noexcept
支持函数表达式,以动态地决定是否抛出异常。这使得异常规范在一些特定的情况下更加灵活和动态。C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父
子类层次结构组织起来的,如下所示:
说明:实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一样自己定义一套异常继承体系。因为C++标准库设计的不够好用。
int main()
{
try {
vector 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;
}
C++异常的优点:
// 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;
}
C++异常的缺点:
以上便是关于 c++11 有关异常的全部知识。接下来,简单的回顾下本文!!!
到此,关于本篇便到此为止了。感谢大家的观看与支持!!!