写点什么

C++ 异常处理机制

作者:正向成长
  • 2022 年 2 月 23 日
  • 本文字数:3119 字

    阅读完需:约 10 分钟

C++异常处理机制

异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。典型的异常包括失去数据库连接以及遇到意外输入等。从去年开始有意识地开始训练自己的思维方式,希望自己可以找到一套相对通用的学习模板,目前觉得相对适用的就是whatwhenwhyhow,关于why看到的资料不多,下面是在笔记中记录的一段话,当时没有写下出处,它从语言设计的层面考虑了 C++中为什么添加异常,觉得很棒,分享给大家:

C++异常主要为设计容错程序提供语言级支持。


为什么需要异常?

自从 C 被发明初来,C 程序员就习惯使用错误代码,但如果一个函数通过设置一个状态变量或返回错误代码来表示一个异常状态,没有办法保证函数调用者将一定检测变量或测试错误代码。结果程序会从它遇到的异常状态继续运行,异常没有被捕获,程序立即会终止执行。由此看来异常不能忽略。 C 程序员能够仅通过 setjmp 和 longjmp 来完成与异常处理相似的功能。 但是在 C++中使用 longjmp,存在一些缺陷,当它调整堆栈时不能对局部对象调用析构函数(VC++能保证这一点,但不要依赖这一点)。  如果你需要一个方法,能够通知不可被忽略的异常状态,并且搜索栈空间(searching the stack)以找到异常处理代码并且确保局部对象的析构函数必须被调用,此时就需要使用 C++的异常处理。


接下来,来了解 C++的异常处理机制。


C++异常处理机制通过异常检测和异常处理两部分进行支持,包括:

  • throw表达式,异常检测部分使用throw表达式抛出异常。

  • try语句块,异常处理部分采用try语句块进行异常处理,try语句块以关键字try开始,并以一个或多个catch子句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码(exception handler)。

  • 一套异常类,用于在throw表达式和相关的catch子句之间传递异常的具体信息,C++中提供了标准exception


简单的异常处理

在 C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在 4 个头文件中:

  • exception 头文件定义了最通用的异常类 exception。它只报告异常的发生,不提供任何额外信息。

  • stdexcept头文件定义了几种常用的异常类

  • new头文件定义了bad_alloc异常类型,默认,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用 new 的方式来阻止它抛出异常:

// 如果分配失败,new抛出std::bad_allocint *p1 = new int;
// 如果分配失败,new返回一个空指针int *p2 = new (nothrow) int;
复制代码


  • type_info 头文件定义了 bad_cast 异常类型,dynamic_cast<type&>(e)如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个bad_cast异常。

#include <iostream>#include <exception>
void tryException() { // 此处是模拟处理出现问题,抛出异常 throw std::runtime_error("This's just exception test!");}
int main(int argc, char* argv[]) { try { tryException(); } catch(const std::runtime_error& e) { std::cout << "catch runtime_error for " << e.what() << std::endl; }}
复制代码


函数在寻找处理代码的过程中退出

当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的 catch 子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的 catch 子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的 catch 子句为止。如果最终还是没能找到任何匹配的 catch 子句,程序转到名为 terminate 的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

-- 摘自《C++Primer》5.6.2 节


异常规格

异常规格对函数定义进行限定,指出其可能引发那些类型的异常,同时提醒程序员此处可能抛出异常应该使用try-catch和处理程序,其由 throw 关键字和异常类型列表组成,异常类型括在括号中,用逗号分割。函数的异常规格不仅要包含自身触发的异常类型,还要调用其他函数可能触发的异常类型。异常规则在 C++11 中被舍弃,在 C++17 中被彻底移除,改用noexcept(true)noexcept(false),前者表示该实现无异常抛出,后者表示所有的异常。

// 可能抛出任何异常void func();
// 只能抛出char*和int类型的异常void func() throw(const char*, int);
// 不抛出任何异常void func() throw();
复制代码

意外异常

如果带异常规格的函数抛出了和规格列表中不匹配的异常,称之为意外异常,会调std::unexpected,该函数默认会调用std::terminate,用户也可以通过std::set_unexpected设定用户自己设定的行为

typedef void (*unexpected_handler)();unexpected_handler set_unexpected(unexpected_handler f) throw();void unexpected();
复制代码

unexpected_handler函数行为遵守严格的限制:

  • 通过调用terminate(默认)、abort()exit()来终止程序。

  • 引发异常,结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数异常规范:

  • 新引发的异常和原异常规范匹配,则程序寻找与新引发异常匹配的catch块,该行为相当于用预期异常取代意外异常。

  • 新引发的异常和原异常规范不匹配且异常规范没有std::bad_exception类型,则程序将调用terminate()

  • 新引发的异常和原异常规范不匹配且异常规范包含std::bad_exception类型,则不匹配的异常将被std::bad_exception异常所取代

#include <iostream>#include <exception>
void myUnexpected() { std::cout << "catch unexpected exception!" << std::endl; // 1. 将意外异常统一转化为bad_exception,或者其他的异常 // throw std::bad_exception(); // 2. 抛出和异常规格匹配的异常,程序寻找匹配的catch处理块 throw "----------- satify exception specification --------";}
// 异常规格,该接口支持抛出const char*的异常// void handleMessage() throw(const char*, std::bad_exception){ // for understandingvoid handleMessage() throw(const char*) { std::cout << __func__ << std::endl; throw std::underflow_error("error::underflow");}
int main(int argc, char* argv[]) { try { std::set_unexpected(myUnexpected); handleMessage(); } catch (const char* e) { std::cout << e << std::endl; } catch (std::bad_exception& e) { std::cout << "catch bad_exception: " << e.what() << std::endl; } return 0;}
复制代码


未捕获异常

没有try块或者catch块时,异常未捕获,称之为未捕获异常。默认,将会导致程序异常终止。也可以修改程序对意外异常和未捕获异常的反应。未捕获的异常,通常不会立刻导致程序终止,程序首先会调用terminate()此时会调用std::abort()函数,我们可以修改terminate()的实现来实现行为定制。相关接口定义:

typedef void (*terminate_handler)();terminate_handler set_terminate(terminate_handler f) throw();void terminate();
复制代码


假设程序抛出了一个未捕获的运行时异常,并自定义终止terminate_handler

#include <iostream>#include <exception>
void myTerminateHandler() { std::cout << "Error catch uncatched exception!" << std::endl; exit(-1);}
int main(int argc, char* argv[]) { // 自定义terminate_handler行为 std::set_terminate(myTerminateHandler); // 抛出一个未捕获的异常,将会触发terminate_handler throw std::runtime_error("This's just a test"); return 0;}
复制代码

参考资料

  • C++ Primer 中文版 第五版


发布于: 刚刚阅读数: 2
用户头像

正向成长

关注

正向成长 2018.08.06 加入

想要坚定地做大规模数据处理(流数据方向),希望结合结合批处理的传统处理方式,以及之后流批混合处理方向进行学习和记录。

评论

发布
暂无评论
C++异常处理机制