异常与错误
传统错误处理
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)