C语言中最常用的处理简单错误的方式就是通过 assert , 错误码 以及 perror等库函数来进行错误的获取和处理. 但是这存在一些缺陷, 比如说 assert 直接终止程序太粗暴, 错误码在返回值为整型家族的函数中会难以判断.
setjmp.h
在C语言的函数库中存在 setjmp 和 longjmp 的组合, 来实现简单的异常捕获和处理. 这在较大型的C语言项目中可能会用到.
#include
#include
jmp_buf jb; // 定义状态
double divison(double a, double b) {
if (b == 0) {
longjmp(jb, -1); // 类似于 throw
} else {
return (double)a / (double)b;
}
}
void printError(int e_id) {
if (e_id == -1) {
printf("除数为 0 ...\n");
} else {
printf("未知错误...\n");
}
}
int main() {
int e = setjmp(jb); // 设置异常状态, 初始为 0
if (e == 0) { // 类似于 try
printf("%f\n", divison(4, 0));// 异常回到setjmp, 修改e
} else { // 类似于 catch
printError(e);
}
return 0;
}
异常可以认为是处理错误的一种方式, 当一个函数发现自己无法处理该错误时就可以选择抛出异常(throw), 让某个函数直接或间接的捕获(try)并处理(catch)这个异常.
异常的抛出, 捕获和处理:
异常可以抛出任意类型的对象, 在处理异常时根据抛出对象的类型进行匹配处理.
#include
using namespace std;
double division(const double a, const double b) {
if (b == 0) {
throw "divisor is zero...";
} else {
return (double)a / (double)b;
}
}
void Fun() {
double a = 0, b = 0;
cout << "请输入 2 个数:" ;
cin >> a >> b;
cout << "相除结果为 " << division(a, b) << endl;
}
void Exception() {
try {
Fun();
} catch (const char* errmsg) {
cout << __LINE__ << ":" << errmsg << endl;
} catch (...) { // 不知道是什么异常, 即处理任何异常
cout << "unknown exception" << endl;
}
}
int main() {
Exception();
return 0;
}
void Fun() {
double a = 0, b = 0;
cout << "请输入 2 个数:" ;
cin >> a >> b;
try {
cout << "相除结果为 " << division(a, b) << endl;
} catch (const char* errmsg) {
cout << __LINE__ << ":" << errmsg << endl;
}
}
// 在 Exception 函数中调用了 Fun, 并且两个函数都进行了异常的捕获, 但是Fun离在调用链中离抛出对象更近, 最先被调用处理, 后面则不再被处理
异常的重新抛出:
一般在两个常见的场景下会使用异常的重新抛出, 分别是释放对内存和请求重试
void Fun() {
int* num = new int(1);
double a = 0, b = 0;
cout << "请输入 2 个数:" ;
cin >> a >> b;
try {
cout << "相除结果为 " << division(a, b) << endl;
} catch (...){
cout << "delete num" << endl;
delete num;
throw; //再次抛出, 让 divisor 为 0 的异常继续抛出
}
cout << "deleted" << endl;
delete num;
}
#include
using namespace std;
int num = 0;
int getNum() {
if (num != 0) {
--num;
throw "num is not zero";
} else {
return num;
}
}
void Catch() {
int choice = 5;
while (choice--) { //重试 5 次
try {
cout << getNum() << endl;
break;
} catch(const char* errmsg) {
cout << errmsg << endl;
} catch(...) {
cout << "Unknown error" << endl;
}
}
if (choice <= 0) {
cout << "failed..." << endl;
} else {
cout << "successed..." << endl;
}
}
int main() {
cout << "please input a num:" << endl;
cin >> num;
Catch();
return 0;
}
异常安全:
1.构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有 完全初始化
2.析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句 柄未关闭等)
3.C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和 unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题
异常规范:
1.异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接 throw(类型),列出这个函数可能抛掷的所有异常类型。
2.函数的后面接throw(),表示函数不抛异常。
3.若无异常接口声明,则此函数可以抛掷任何类型的异常
// 这里表示这个函数会抛出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 new (std::size_t size, void* ptr) throw();