C语言传统的处理方式
- 终止程序:在发生错误时直接终止程序的运行,可以通过assert宏来进行实现。如
assert(condition)
,其中condition
不满足要求时,将会使程序立刻停止执行,并输出相关错误信息。这种方式的确定是用户很难接受突然的程序终止,它没有提供任何机会来进行修复和处理。 - 返回错误码:在函数体内,如果发生错误,将错误码作为函数的返回值进行返回。通常情况下0表示返回成功,非零值表示错误,并且根据对应返回值做出相应的处理。
这些方式在实际应用中对代码的可读性和可维护性是非常低的,尤其在大型项目中。
C++异常的概念
在 C++ 中,异常是指程序在运行过程中遇到的一些意外情况或错误,导致当前代码无法正常执行下去的情况。这些异常可能是由于程序逻辑错误、外部环境变化或者运行时错误引起的。
异常处理机制允许程序员在代码中标识出可能引发异常的代码块,并提供一种机制来捕获和处理这些异常,以便程序在出现问题时能够以一种更加优雅和可控的方式来处理异常情况,而不至于导致程序崩溃或者无法正常执行。
在 C++ 中,异常处理的主要机制包括以下几个关键组件:
-
抛出异常(Throwing Exceptions):当程序遇到错误或异常情况时,可以使用
throw
关键字来抛出一个异常。异常可以是任何类型的数据,但通常是标准库中的异常类或者自定义的异常类的实例。 -
捕获异常(Catching Exceptions):在代码中使用
try
块来标识可能会引发异常的代码段,然后使用catch
块来捕获和处理这些异常。catch
块可以指定捕获的异常类型,以及对应的处理逻辑。 -
异常传递(Exception Propagation):如果在
try
块中的代码抛出了异常,程序会在当前函数中查找匹配的catch
块来处理异常。如果找不到匹配的catch
块,则异常会在当前函数中被终止,并且会将异常传递给调用栈上的上一层函数,继续寻找匹配的catch
块。 -
清理资源(Resource Cleanup):在异常处理过程中,可以使用
finally
块来执行一些清理工作,确保资源在程序终止时得到正确释放。
使用异常处理机制可以使程序的错误处理逻辑更加清晰和灵活,提高了程序的健壮性和可维护性。
异常的使用
异常的抛出和匹配规则
异常的抛出和匹配规则是异常处理机制的核心。
异常抛出和匹配规则的基本概念:
-
异常的抛出(Throwing Exceptions):
- 异常可以由
throw
关键字抛出。抛出异常时,可以使用任何类型的表达式,通常是一个异常对象的实例,例如:throw std::runtime_error("Something went wrong");
- 抛出的异常可以是任何类型,包括标准库提供的异常类型,或者用户自定义的异常类的实例。
- 异常可以由
-
异常的匹配(Exception Matching):
- 异常的匹配是指在
try
块中捕获并处理异常的过程。 - 当
try
块中的代码抛出异常时,程序会在try
块后面的catch
块中寻找与抛出的异常类型相匹配的处理程序。 catch
块可以使用异常类型来指定要捕获的异常。如果抛出的异常的类型与catch
块中指定的类型匹配,那么相应的catch
块就会执行。- 异常类型可以是异常类的实例、指针、引用或者任何继承自
std::exception
的类型。
- 异常的匹配是指在
-
异常的传递(Exception Propagation):
- 如果在
try
块中的代码抛出异常,程序会在当前函数中查找匹配的catch
块来处理异常。 - 如果当前函数中没有匹配的
catch
块,那么异常会传播到调用当前函数的函数中,依此类推,直到找到匹配的catch
块为止。 - 如果在调用栈上找不到匹配的
catch
块,程序会调用std::terminate()
来终止程序的执行。
- 如果在
简单使用
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;
cout << "=====================" << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
异常的缺陷
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void fxx()
{
int i = 0;
cin >> i;
if (i % 2 == 0)
{
throw 1;
}
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
try
{
fxx();
}
catch (int x)
{
cout <<__LINE__<<"捕获异常:" << x << endl;
}
cout << "=====================" << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (int x)
{
cout << __LINE__ <<"捕获异常:"<< x << endl;
}
cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
return 0;
}
异常安全
异常安全是指程序在面对异常时依然能够保持系统的一致性和稳定性,不会导致资源泄漏或数据损坏。异常安全性分为三个级别:
-
基本异常安全(Basic Exception Safety):即使发生异常,程序的内部状态不会被破坏。程序能够保证在异常抛出前的状态与异常抛出后的状态之间存在一种合理的约束关系。例如,内存不会泄漏,对象不会处于不一致的状态。
-
强异常安全(Strong Exception Safety):即使发生异常,程序的内部状态也不会改变。程序能够保证在异常抛出前的状态与异常抛出后的状态完全一致。这意味着所有操作要么成功完成,要么没有执行任何修改。通常需要使用事务性或回滚操作来实现。
-
无异常安全(No-Throw Guarantee):程序不会抛出任何异常。这意味着所有的操作都能够在不引发异常的情况下成功完成。通常用于对性能和可靠性要求极高的系统,例如实时系统。
实现异常安全的方法通常包括以下几种:
- 使用 RAII(资源获取即初始化)技术,通过对象的生命周期来管理资源,确保在对象销毁时资源得到正确释放。
- 在异常发生时使用异常处理机制来回滚操作,恢复到先前的状态。
- 使用事务性操作来确保操作的原子性,当发生异常时可以回滚整个操作。
- 使用智能指针、容器等标准库提供的异常安全的工具和数据结构,减少手动管理资源的复杂度。
未知异常
一般来说,如果我们不确定哪个地方会出现错误,我们可以增加一个捕获模块:用来显示未知的异常情况
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void fxx()
{
int i = 0;
cin >> i;
if (i % 2 == 0)
{
throw 10;
}
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
fxx();
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const string& err)
{
cout << err << endl;
}
catch (...) // 任意类型的异常
{
cout << "未知异常" << endl;
}
return 0;
}
异常继承体系
这里,将模拟一般情况异常抛出的情况,利用多态的原理来捕获对应哪个流程的异常。
class Exception
{
public:
Exception(const string& errmsg, int id)
:_errmsg(errmsg)
, _id(id)
{}
virtual string what() const
{
return _errmsg;
}
protected:
string _errmsg; // 错误描述
int _id; // 错误编号
};
// 继承和多态
class SqlException : public Exception
{
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;
};
class CacheException : public Exception
{
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(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
cout << "!!!!!!!!!!!!!!!!!!!!!!!!运行成功" << endl;
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
SQLMgr();
}
void HttpServer()
{
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
srand(time(0));
// 20:15继续
while (1)
{
Sleep(1000);
try {
HttpServer(); // io
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}