以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/IkSvO8bP0…
这是《掌握 C++ 异常艺术:构建健壮程序的秘诀与实战策略》系列文章的第四篇,关注我可以查看系列里其它文章。
有些场景下,需要重新抛出当前捕捉到的异常信号,有个比较便捷的语法:
throw;
在关键词 throw 之后不跟任何的异常信号,就表示再次抛出当前已捕获的异常信号。
这种写法真的有点讨巧,不过基于这样的语法,可以方便我们实现两方面的特性功能:
简单的堆栈跟踪
为了方便调试和后期排查问题的起因,问题发生后都希望能查看异常发生时的程序执行路径,包括从程序的入口点到当前位置(通常是异常发生的地方)。
而堆栈跟踪就记录了特定时刻的调用栈状态,那么如何利用异常的再抛出便利,来简单实现堆栈跟踪的类似形式?看下面的例子:
class MyException
: public std::exception
{
public:
MyException()
: std::exception(),
str("MyException ") { }
virtual const char* what()
const noexcept override {
return str.c_str();
}
void addInfo(const std::string& info) {
str += info;
}
private:
std::string str;
};
void g()
{
try {
// ...
throw MyException();
} catch (MyException& e) {
std::stringstream ss;
ss << __func__ << "() failed\n";
e.addInfo(ss.str().c_str());
throw;
}
}
void f()
{
try {
g();
} catch (MyException& e) {
std::stringstream ss;
ss << __func__ << "() failed\n";
e.addInfo(ss.str().c_str());
throw;
}
}
int main()
{
try {
f();
} catch (const std::exception& e) {
std::cerr << "catch err: "
<< e.what() << std::endl;
}
return 0;
}
代码中,基于异常类 std::exception 派生一个子类 MyException,然后实现接口 addInfo
,用于在原有异常信息基础上添加新的异常信息。
调用会抛出 MyException 异常信号的函数 f() (间接抛出是为了演示多层调用栈的展开)后,捕捉(catch)异常信号,捕获成功则调用异常信号的 addInfo 接口用于添加当前调用栈的信息。
__func__
是预定义的内置标识符,代表当前函数名。
然后利用 throw;
再次抛出异常信号,这时异常信号已经包含当前调用栈的信息了,因而后续调用栈展开的过程中,随时都可以获取异常发生后已被展开的各级调用栈的相关信息。
程序跑起来,看看效果:
catch err: MyException g() failed
f() failed
异常分发器
代码中业务众多,需要考虑的异常处理自然也多,如果处理比较分散,就会产生多而杂的问题,那么如何简化异常处理呢?
将捕获后的异常集中起来处理,那么管理维护就变得方便简单,这需要实现类似调度器的功能,这里姑且叫它「异常分发器」吧。
下面举个小栗子:
void handleException()
{
try {
throw;
} catch (const MyException1& e) {
// ...code to handle MyException1...
} catch (const MyException2& e) {
// ...code to handle MyException2...
}
// ...
}
void f()
{
try {
// ...something that might throw...
} catch (...) {
handleException();
}
}
void g()
{
try {
// ...something that might throw...
} catch (...) {
handleException();
}
}
上面的代码里,handleException() 函数负责统一处理异常信号。当在 f() 函数中捕获到异常时,会调用 handleException() 函数来处理异常。
但是,如果需要 handleException() 函数来处理已捕获的异常信号,那么是不是就要往这函数参数传递信号?
handleException() 函数中的 try 块会利用 throw;
重新抛出当前已捕获的异常信号,就算不知道当前已捕获的异常信号具体是什么类型也无所谓,然后 catch 块再次匹配捕捉不同的异常类型进行处理,这样调用 handleException() 函数时,可免除将异常信号作为参数的传递,也就不用考虑需要兼容各种类型异常信号的麻烦。
实现异常分发器使得异常信号的处理逻辑可以高度集中,提高了代码的复用性和可维护性。
使用 throw;
重新抛出当前异常信号,使得异常处理变得更加灵活和高效。无论是为了调试还是提高代码的健壮性,这种写法都提供了很大的帮助。在实际项目中,运用好这种技巧,可以显著提升代码质量和调试效率。
各位看官可以再发散一下思维,或许有用的地方不止这两种。