处理大型程序的3大特性:
- 异常处理:协同处理错误
- 命名空间:协同开发
- 多重继承:复杂建模
异常处理
- 异常处理机制:允许在运行时对出现的问题相应处理,它将检测和处理分离开,无需知道处理的具体细节
- throw表达式,抛出 || 引发 异常 :创建异常对象
- try块{ throw表达式}:接受抛出异常的部分
- 异常对象传递给异常声明 参数,并匹配成功后,执行这个catch块
- catch子句块(异常声明 形参列表){ 函数体 }:捕获异常:捕获try中的throw表达式,函数体:处理异常
- catch一个或多个……
抛出异常
- throw表达式:必须拥有完全类型,如果是类类型,必须含有public的折构和拷贝构造或移动构造(这样才能被销毁和初始化异常对象)
#include <iostream>
#include <stdexcept>void func3() {std::cout << "Entering func3\n";throw std::runtime_error("Error in func3"); // 抛出异常std::cout << "This line in func3 will not be executed\n";//不会被执行
}void func2() {std::cout << "Entering func2\n";func3(); // 调用 func3std::cout << "This line in func2 will not be executed\n";//不会被执行
}void func1() {std::cout << "Entering func1\n";func2(); // 调用 func2std::cout << "This line in func1 will not be executed\n";//不会被执行
}int main() {try {func1(); // 调用 func1} catch (const std::exception& e) {std::cout << "Caught exception: " << e.what() << std::endl;}return 0;
}//输出
Entering func1
Entering func2
Entering func3
Caught exception: Error in func3
- 调用链函数可能会提早结束,因为throw后的语句块不会执行
- 栈展开过程中,一层一层往上返回,在每一层函数返回时,所有局部对象的析构函数都会被调用,从而销毁这些对象。
栈展开
- 栈展开:在跳转到
catch 块之前,会沿着嵌套函数的调用链不断查找,逐层退出当前函数的调用栈,直到找到一个合适的catch块来处理该异常 - 调用链:在整个嵌套过程中形成的调用链),并创建一系列的对象,比如从main开始调用func1,func1又调用func2
- 一下是具体过程:
- 当throw位于try语句块内,并当执行throw引发异常后,暂停当前函数的执行(类似于return),跟在throw后面的代码将不会被执行,
- 而是会寻找匹配与try关联的catch模块,如果找到了第一个匹配到的catch,就执行这个catch块,当执行完毕,找到try关联的最后一个catch的语句块结束,从这里开始执行
- 如果这一步没有找到,并且try嵌套在其他try语句块内,从外层try继续寻找
- 如果还是没有,退出当前函数,在调用函数的外层函数继续寻找
- 如果最终没有找到匹配,调用标准库函数terminate终止程序,因为异常通常被认为妨碍程序执行的事件
- //
- 自动销毁:
- 在栈展开过程中,会逐层执行代码块,其中创建的局部对象:局部内置类型,局部类对象(折构函数),都会被正确销毁,并且有可能发生在构造函数中,数组,容器的初始化中,初始化的这部分元素,也都会被正确销毁
- //
- 资源释放管理:
- 在栈展开过程中,
- 对于普通函数来讲,如果函数体内抛出异常,函数中throw之后的语句不会执行,很有可能释放资源的部分未被执行
- 如果用类管理资源,即使发生了异常,折构函数总是会被执行,这样就可以保证资源都被正确释放
- 但要注意,折构函数不要抛出自己不能处理了的异常,因为一旦自身没能捕获异常,那么程序将终止
捕获异常
- 异常对象:
- 使用throw表达式对异常对象拷贝初始化,当异常处理完毕,将被销毁
- 表达式的静态类型,决定异常对象的类型
- //
- catch参数:必须拥有完全类型,仅可以为左值引用
- 异常对象会初始化异常声明的参数
- 异常声明的参数与引用:
- 非引用类型会发生拷贝,且在继承关系中,用派生类初始化基类类型,则派生类中特有的数据和行为将会丢失。
- 而如果为引用类型,则不会被拷贝,也不会丢失。
- //
- 匹配规则:
- 从异常对象转为catch参数,支持的有限的转换,否则必须精准匹配:
- 非常量转为常量
- 派生类转为基类
- 数组或函数转为指向数组或函数的指针
- //
- 重新抛出异常
- throw后面不包含任何表达式,且位于catch块内,
- 表明结束当前的try块,沿着调用链上一层(外层),继续处理异常,比较像break
- //
- 捕获所有异常
- catch( ...),通常和throw重新抛出异常,以及多个子catch块一起使用,
noexcept
- 指特定函数不会发生异常
- ……续
异常类层次
- exception头文件定义了通用异常类,是所有异常类的基类,仅报告异常发生,不提供其他行为,
- std::bad_alloc表示内存分配失败,std::bad_cast表示动态类型转换失败,std::bad_typeid表示对空指针类型的
typeid 操作 - stdexcept定义常用的异常类:
std::runtime_error,std::logic_error -
std::runtime_error:
- 表示在运行时检测到的问题。
- 常见派生类:
std::overflow_error: 表示算术运算的溢出。std::underflow_error: 表示算术运算的下溢。std::range_error: 表示范围错误。std::logic_error: 表示逻辑错误。std::domain_error: 表示数学函数的域错误。std::invalid_argument: 表示无效的函数参数。
-
std::logic_error:
- 表示程序逻辑错误,通常在编译时就可以检测到。
- 常见派生类:
std::invalid_argument: 表示传递给函数的无效参数。std::domain_error: 表示参数超出允许的数学函数域。std::length_error: 表示尝试创建过长的对象。std::out_of_range: 表示使用超出范围的值。
- 我们可以自定义异常类,继承字标准异常类,并重写
what() 函数以提供特定的异常信息。 - ……续