通过合理的异常处理机制,程序可以在遇到异常情况时,避免直接崩溃,而是采取合适的措施,如提示用户错误信息、进行数据回滚、尝试重新执行操作等,从而增强程序的健壮性和稳定性
🚩传统的错误处理机制:
assert
,缺陷:用户难以接受。如发生内存错误,除 0
错误时就会终止程序errno
中,表示错误实际中 C
语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误,因此通过异常机制来实现错误的查找回滚修改是很有必要的
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;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
return 0;
}
异常机制是一种错误处理机制,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误
throw
: 当问题出现时,程序会抛出一个异常。这是通过使用 throw
关键字来完成的,即当出错误时,会通过抛出,然后打印错误信息等方式告诉程序员错误的问题是什么try
: try
块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch
块,即将可能会发生错误的函数放在 try
块里catch
: 在您想要处理问题的地方,通过异常处理程序捕获异常,catch
关键字用于捕获异常,可以有多个 catch
进行捕获,根据环境不同选择合适的类型匹配 catch
errmsg
是在异常处理中专门用来存放特定类型异常所携带错误信息的变量,方便后续对异常情况进行处理和提示
那么异常的实现流程是怎么样的呢?
throw
本身是否在 try
块内部,如果是再查找匹配的 catch
语句。如果有匹配的,则调到 catch
的地方进行处理catch
则退出当前函数栈,继续在调用函数的栈中进行查找匹配的 catch
main
函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的 catch
子句的过程称为栈展开,所以实际中我们最后都要加一个 catch(...)
捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止catch
子句并处理以后,会继续沿着 catch
子句后面继续执行try
在查找匹配的 catch
的流程图大概模式如图所示,类似于栈的递归
转换到上述的示例代码,即在 main
函数中,使用 try
块调用 Func
函数。如果 Func
函数中调用的 Division
函数抛出了 const char*
类型的异常,会被第一个 catch
块捕获,并输出异常信息;如果抛出了其他类型的异常,会被第二个 catch
块(使用 ...
表示捕获所有类型的异常)捕获,并输出 “unkown exception
”
🔥值得注意的是:
catch
的处理代码catch
以后销毁(这里的处理类似于函数的传值返回)catch(...)
可以捕获任意类型的异常,问题是不知道异常错误是什么,防止程序直接终止了,也意味着程序抛出了个未知的异常throw
其实也可以有多个,但是这没有意义,第一个 throw
执行之后,就跳到对应的 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()
{
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;
}
这里 Division
函数当 b == 0
时抛出异常,Func
函数内如果不直接捕获异常,跳到外面去 catch
的话,后面的 delete[] array
就不会执行了,会造成内存泄漏,因此需要先捕获异常,使语句正常执行,然后再将异常抛出
诸如此类,像这样为完成操作的抛出,值得注意:
C++
中异常经常会导致资源泄漏的问题,比如在 new
和 delete
中抛出了异常,导致内存泄漏,在 lock
和 unlock
之间抛出了异常导致死锁,C++
经常使用 RAII
来解决以上问题,关于 RAII
我们智能指针这节进行讲解
以 exception 异常类为例,发现 C++98
的异常规范为:
// 这里表示这个函数会抛出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();
异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些,可以在函数的后面接 throw(类型)
,列出这个函数可能抛掷的所有异常类型;函数的后面接 throw()
,表示函数不抛异常;若无异常接口声明,则此函数可以抛掷任何类型的异常
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;
到了 C++11
,因为之前的方式几乎没什么人用,索性改成有或没有异常抛出的选项
// 服务器开发中通常使用的异常继承体系
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 = '张三'");
}
//throw "xxxxxx";
}
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()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1));
try {
HttpServer();
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
不同开发人员可能根据自己的习惯随意抛出各种类型的异常,可能是标准库异常,也可能是自定义的零散异常类。这会导致在捕获异常时,很难使用统一的方式进行处理。例如,有的函数抛出 std::runtime_error
,有的抛出自定义的简单字符串异常,外层调用者需要编写多种不同的 catch
块来捕获,代码会变得冗长且难以维护
所以公司就设计出了继承体系,抛出的异常对象都是继承自某个基类的派生类对象。这样,在捕获异常时,只需要捕获基类类型的异常,利用多态特性,就可以处理所有从该基类派生出来的各种具体异常类型
这里的代码里有一个基类 Exception
,以及继承自它的三个派生类 SqlException
、CacheException
和 HttpServerException
。每个派生类都重写了 what()
方法,用于返回不同的异常信息
C++异常的优点:
bug
boost
、gtest
、gmock
等等常用的库,那么我们使用它们也需要使用异常T& operator
这样的函数,如果 pos
越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误C++异常的缺点:
C++
没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用 RAII
来处理资源的管理问题。学习成本较高C++
标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱func() throw();
的方式规范化总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出这是大势所趋
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有