红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【C++】异常

异常与错误

传统错误处理

C风格的传统错误处理通常通过在出现异常时返回一个异常值(如-1),main函数捕获到了异常值后,进行对应的提示与操作

如sqrt函数中,当输入值小于0时,返回-1,main函数捕获后输出错误信息,因为这里可以保证sqrt操作返回的都是非负数,所以可以设置-1值

进步之处:错误信息的输出应由main决定,因为sqrt函数仅仅为一个计算函数,不应该涉及输入输出流

另一种方法:为了不与值域冲突,可以返回一个pair<int, bool>,bool判断是否合法。但这样也会造成

注意:报错与退出逻辑不应该直接写在被调用函数中,合适的方法应该是返回值

Optional

返回Optional数据类型,该类型可以表示一个可能存在也可能不存在的值

当发现异常值传入,我们可以返回一个空值std::nullopt,让main函数捕获

判断时可以使用以下方法:

  • .has_value()判断返回的是否是异常值
  • 或直接用该类型变量判断是否是异常值(存在一个bool的显式转换)
  • .value()获取正常值

缺点:当有多种错误类型区分的需求便无法处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <optional>
#include <vector>
#include <iostream>

std::optional<int> findIndex(const std::vector<int>& data, int value) {
    for (size_t i = 0; i < data.size(); ++i) {
        if (data[i] == value) {
            return i; // 返回找到的索引
        }
    }
    return std::nullopt; // 未找到
}

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    auto index = findIndex(data, 3);

    if (index) {
    	std::cout << "Found at index: " << *index << std::endl;
    } else {
    	std::cout << "Value not found" << std::endl;
    }

    return 0;
}

错误码

通过设置一个全局变量错误码,当出现对应错误时,设置对应错误码并返回-1,main函数捕获并根据错误码处理

错误码的设置可以参考errno.h中的宏定义设置

使用strerror(errno)来输出对应错误

使用setlocate(LC_ALL, "zh-CN.UTF-8")

使用perror 在错误输出前加上一段描述加一个冒号

异常

错误码的缺点:无法自动穿透调用栈,必须编写代码并每一层都检查并保存错误码,一直传递,有时候还需要转换;并且错误码无法携带更多完整信息

这时我们需要使用异常,包含三个部分:try,catch,throw

  • try用于检查后面大括号中代码块所产生的异常
  • catch用于捕获和处理上述异常
  • throw用于在try中抛出异常

其中,throw抛出的异常可以是标准库中定义的异常类,传入string类型的参数用于描述异常

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

double divide(double a, double b){
  if (b==0){
    throw runtime_error("Divided by zero");
  }
  return a/b;
}

int main(){
  try{
    int c= divide(1,0);
    cout << c << endl;
  }
  catch(runtime_error& e){
    cout <<"exception: "<<e.what()<<endl; 
  }
}

throw

C++中可以将各种类型的变量、对象与指针作为异常抛出

对应在catch代码中要设置为对应类型即可捕获到对应异常

一般推荐使用自定义或标准库中的异常类,如可以创建错误类继承自std::exception类,并手动修改异常

catch

catch代码块只能捕捉到括号中指定的类型,如果捕获的是类对象,则可以捕获到该类以及继承类中的对象

一个try代码块可以跟多个catch代码块,原则:越专用的越靠前,越通用的越靠后

对于未知异常,可以使用catch(...)捕获,不过不建议捕获自己代码无法处理的异常,而是留给能处理的代码或者让程序终止

资源管理

当函数中内存释放的部分出现在了异常后,则会在抛出异常后结束函数,从而导致内存泄漏

我们可以使用类进行封装,当类对象被自动释放时,会调用析构函数进行释放

我们也可以使用智能指针来封装,当抛出异常后,封装的对象会被自动释放

上述资源管理的方式称为RAII(Resource Acquisition Is Initialization)