EXCEPTION——出人意料的异常

知识铺垫

首先我们要了解一下,什么是异常Exception:

异常Exception:
An exception is an indication of a problemthat occurs during a
program’s execution.
程序执行期间,可检测到的不正常情况
例如:除数为零、数组下标越界、打开不存在的文件、内存分配失败等

常见的标准库异常类:
在这里插入图片描述

  • invalid_argument异常:接收到一个无效的实参,抛出该异常
  • out_of_range异常:收到一个不在预期范围中的实参,则抛出
  • length_error异常:报告企图产生“长度值超出最大允许值”的对象
  • bad_alloc异常:当new()操作符不能分配所要求的 存储区时,会抛出该异常。它是由基类exception派生的
  • overflow_error异常:报告算术溢出错误
  • underflow_error异常:报告算术下溢错误
以上异常被包含在stdexcept库中

基础处理方法

在很多情况下,当异常出现后,我们需要合理的处理异常,使程序能够继续运行
常见的处理异常的方法就是:

try -> catch -> throw

基本结构就是:

//捕获并处理异常的程序段 
try 
    throw 异常对象;   
catch(异常类型声明)   
    复合语句 
catch(异常类型声明) 
    复合语句 
...
  • throw操作创建一个异常对象并抛掷
  • catch子句按其出现的顺序被检查,匹配的catch子句将捕获并处理异常(或继续抛掷异常)
  • 特定异常类型变量的声明,如 catch(bad_alloc& theexception)
  • 如要捕捉所有的异常,则使用 catch( … )
throw 对象名
  • throw表达式:在try块中,用于抛出异常,是异常处理的第一步
  • throw表达式也可以抛出任何类型的对象,如枚举、整数等等,但最常用的是类对象
catch(异常类型声明){ 复合语句 }
  • 包括三个部分:
    • 关键字catch
    • 圆括号中的异常声明
    • 复合语句
  • 异常类型声明:可以是基本数据类型,也可以是用户自定义类型。只能声明一种类型
catch (…) { 复合语句 }
  • catch_all子句
  • 任何异常都可以进入这个catch子句
  • 这里的三个点称为省略号
  • 花括号中的复合语句用来执行指定操作
  • 异常发生后按栈展开(stack unwinding)退出,动态分配的非类对象资源不会自动释放的,通常在catch_all子句中释放
  • catch_all子句可以单独使用,也可以与其它catch子句联合使用
    如果联合使用,则必须放在相关catch子句表的最后
  • 如果catch_all子句放在前面进行某项操作,操作完成之后该异常还需要进入特定的catch语句中进行其他操作,则应由catch子句重新抛出异常,逆调用链去查找匹配的处理子句来处理
程序执行步骤:
  1. 抛出异常时,跳过try块中的后续语句,try块结束,程序控制转至catch语句
  2. 寻找匹配的catch handler,遵循 " is a " 准则,即如果抛出的异常一个对象,则派生类对象可以与基类的catch handler成功匹配
  3. 执行catch handler代码
  4. 执行后,跳过剩余的catch语句,程序控制跳至try-catch块后的首条语句
  5. 若无匹配的catch handler,则交给C++标准库中定义的terminate()处理异常, 其缺省功能是调用abort()终止程序
#include <iostream> 
using namespace std; 
void fun(int x) 
{ 
    try { 
        if (x==0) throw x+5; 
        if (x==1) throw 'X'; 
        if (x==2) throw "a book"; 
        if (x==3) throw 3.14; 
        if (x==4) throw true; 
    } 
    catch(int i) {cout<<"catch an integer "<<i<<endl;} 
    catch(char c) {cout<<"catch a char "<<c<<endl;} 
    catch(char str[10]) {cout<<"catch a string "<<str<<endl;} 
    catch(double d) {cout<<"catch a double "<<d<<endl;} 
    catch (...) {cerr << "caught other exception (non-compliant compiler?)\n";} 
} 

int main() 
{ 
    fun(0); 
    fun(1); 
    fun(2); 
    fun(3); 
    fun(4); 
    return 0; 
} 

// Output
catch an integer 5
catch a char X
catch a string a book
catch a double 3.14
catch other exception (non-compliant compiler?

在这里强调一下异常处理的适用情况:

  • 异常处理只用于异常情况
  • 程序本身的局部错误可用传统控制方法
  • 处理库函数产生的错误时通常用异常

处理除数为零的异常

#include<iostream>   
using namespace std; 
int divd(int x,int y) { 
    if (y==0) throw y;    //抛出异常
    return x/y; 
} 
int main() 
{ 
    try { 
        cout<<"5/2="<<divd(5,2)<<endl; 
        cout<<"8/0="<<divd(8,0)<<endl; 
        cout<<"7/1="<<divd(7,1)<<endl; 
    } 
    catch (int) {cout<<"except of deviding zero.\n";} 
    cout<<"that is ok.\n"; 
} 

之前我们强调过:异常类型声明可以是基本数据类型,也可以是用户自定义类型。只能声明一种类型
所以我们利用runtime_error异常类派生出一个新的类,重写上面的代码

#include<stdexcept>
using std::runtime_error
class DivideByZeroException:public runtime_error {
//继承runtime_error类
public:
    DivideByZeroException():runtime_error("attempt to divided by zero") {}  
};

这里我们可以注意到,C++标准类runtime_error在构造的时候,可以提供一个字符串类型的参数,初始化异常的类型
当在程序中调用what成员函数时,就可以输出这段字符串了

#include"DivideByZeroException.h"

double quotient(int numerator,int denominator)
{
    if (denominator==0)
        throw DivideByZreoException();
    return static_cast<double>(numerator)/denominator;
}

int main() 
{
    int number1,number2;
    double result;
    cout<<"Enter two integers (end-of-file to end):\n";
    while (cin>>number1>>number2)
    {
        try
        {
            result=quotient(number1,number2);
            cout<<"The quotient is: "<<result<<endl;
        }
        catch (DivideByZeroException &divideByZero) 
        {
            cour<<"Exception occurred: "<<divideByZero.what()<<endl;
        }
        cour<<"\nEnter two integers (end-of-file to end):\n";
    }
    return 0;
}

异常再抛出

我们在介绍catch_all子句的时候,说过这样一段话:

如果catch_all子句放在前面进行某项操作,操作完成之后该异常还需要进入特定的catch语句中进行其他操作,则应由catch子句重新抛出异常,逆调用链去查找匹配的处理子句来处理

小朋友你是否有一个疑问:怎么重新抛出异常?
其实这个问题很简单,只需要短短一个词的指令:

throw; //异常再抛出
  • 由外层的catch捕捉
  • 外层无catch处理器时,调用terminate终止程序

栈展开

throw可以抛出临时对象(异常对象)

try 
{
    if (IsEmpty()) throw popOnFull<T>(data);
    catch (popOnFull<T> eObj) { 
        cerr<<"栈满"<<eObj.getvalue()<<"未压入栈"<<endl; 
    } 
}

这个异常对象在抛出点被创建,catch子句处理完之后生命周期结束
catch子句的异常声明与函数参数声明类似,可以是按值传送,也可以按引用传递

对于大型类,建议按引用传递
catch (pushOnFull<T> &eObj) { 
    cerr<<"栈满"<<eObj.getvalue()<<"未压栈"<<endl;
}

下面我们就要来介绍异常处理中最重要的一个机制了:Stack Unwinding(栈展开机制)

  • 当某个函数(异常源)抛出异常,将立刻结束该函数的执行,根据函数调用链回溯(可以是本函数)寻找可以catch该异常的Handler
  • 如果找到了匹配的Handler,则执行Stack Unwinding,一次释放从异常源到Handler所在函数的所有局部对象
  • 如果在main函数中仍没有找到匹配的Handler,则调用terminate函数(该函数缺省调用abort)
#include<iostream>
#include<stdexcept>
using namespace std;

void function3() {
    cout<<"In function3"<<endl;
    throw runtime_error("runtime_error in function3");
}

void function2() {
    cout<<"function3 is called inside function2"<<endl;
    function3();
}

void function1() {
    cout<<"function2 is called inside function1"<<endl;
    function2();
}

int main()
{
    try {
        cout<<"function1 is called inside main"<<endl;
        function1();
    }
    catch (runtime_error &error) {
        cout<<"Exception occurred: "<<error.what()<<endl;
        cout<<"Exception handled in main"<<endl;
    }
}

// Output
function1 is called inside main
function2 is called inside function1
function3 is called inside function2
In function3
Exception occurred: runtime_error in function3
Exception handled in main

构造函数,析构函数和异常处理

异常对象被catch语句匹配时,为已构造的所有局部对象自动调用析构函数
如果catch语句的异常类型声明的是一个值参数, 则其初始化方式是复制被抛出的异常对象
如果是引用,则初始化方式是使用引用指向异常对象

#include <iostream> 
#include <string> 
using namespace std; 

class Except {
public: 
    Except() {} 
    Except(const Except &) {cout<<"Copy Constructor is called\n";} 
    ~Except(){} 
    const char *showReason() const {return "Except类异常";} 
}; 

class A { 
public: 
    A() {cout<<"构造A."<<endl;} 
    ~A() {cout<<"析构A."<<endl;} 
};

void Func() { 
    A a; 
    cout<<"在Func中抛出Except类异常"<<endl; 
    throw Except(); 
    cout<<"会显示吗?"<<endl; 
} 

int main() 
{ 
    cout<<"在main函数中"<<endl; 
    try { 
        cout<<"在try块中调用Func()"<<endl; 
        Func(); 
    }
    catch(Except e) {cout<<"捕获到Except类型异常:"; cout<<e.showReason()<<endl;}
    catch(...) {cout<<"捕获到其它异常"<<endl;} 
    cout<<"回到main()函数,恢复执行"<<endl; 
    return 0; 
}

继承和异常处理

如果catch捕获基类类型异常对象的指针或引用, 则可以捕获该基类所派生的异常对象的指针或引用,即允许多态处理错误

exception类的接口如下:

namespace std { //注意在名字空间域std中 
    class exception { 
    public: 
        exception(); //缺省构造函数,指定该函数不抛出任何异常 
        exception(constexception &); //拷贝构造函数 
        exception &operator=(const exception&); //赋值操作符重载 
        virtual ~exception(); //析构函数 
        virtual constchar* what()const; //返回一个C风格的字符串,为抛出的异常提供文本描述 
    }; 
}

处理new的异常

如果我们使用newf申请内存时失败,且set_new_handler并没有声明new handler函数,new指令会抛出一个bad_alloc类型的异常
我们有三种方法,来处理这个异常:

  • 声明new handler函数
  • 使用unique_ptr类
  • 直接处理bad_alloc异常

Exception handle典型操作:

  • 向用户报告错误
  • 将错误记录到文件中
  • 优美地终止程序
  • 尝试用另一种策略来完成失败的任务
声明new handler函数
  • 函数set_new_handler在出现内存申请失败的时候,会被new指令调用
  • 函数指针作为参数,该函数没有参数和返回值
  • C++中标准的new-handler函数
    • 创造更多的内存,并且再次尝试new申请内存
    • 抛出一个bad_alloc类型的异常,或者
    • 调用函数abort或exit终止程序
#include <iostream> 
using std::cerr; 
using std::cout; 
 
#include <new> // standard operator new and set_new_handler
using std::set_new_handler;                                 

#include <cstdlib> // abort function prototype 
using std::abort;

// handle memory allocation failure
void customNewHandler() {
    cerr<<"customNewHandler was called";
    abort();
}

// using set_new_handler to handle failed memory allocation
int main()
{
    double *ptr[50];
    
    // specify that customNewHandler should be called on 
    // memory allocation failure
    set_new_handler(customNewHandler); 
    // allocate memory for ptr[i]
    // customNewHandler will be called on failed memory allocation
    for (int i=0;i<50;i++) {
        ptr[i]=new double[50000000]; // may throw exception
        cout<<"Allocated 50000000 doubles in ptr["<<i<<"]\n";
    }
    return 0;
}
使用unique_ptr类

C++智能指针是包含重载运算符的类
其行为像常规指针,但智能指针能够及时、妥善地销毁动态分配的数据
智能指针类重载了解除引用运算符 * 和成员选择运算符 ->
unique_ptr是从C++ 11开始,定义在< memory >中的智能指针,持有对对象的独有权
当超出作用域时,智能指针所指向的对象会被自动释放

class Integer {
public: 
    Integer(int i=0); 
    ~Integer(); 
    void setInteger(int i); 
    int getInteger() const; 
private: 
    int value;
}; 

Integer::Integer(int i):value(i) {cout<<"Constructor for Integer "<<value<<endl;} 

Integer::~Integer() {cout<<"Destructor for Integer "<<value<<endl;}

void Integer::setInteger(int i) {value=i;} 

int Integer::getInteger() const {return value;}
 
int main() { 
    cout<<"Creating a unique_ptr object that points to an Integer\n";
    unique_ptr<Integer> ptrToInteger(new Integer(7)); 
    cout<<"\nUsing the unique_ptr to manipulate the Integer\n";
    ptrToInteger->setInteger(99); 
    cout<<"Integer after setInteger: "<<(*ptrToInteger).getInteger()<<"\n\nTerminating program"<<endl;
}
直接处理bad_alloc异常
int main() 
{ 
    double *ptr[50]; 
    try { 
        for (int i=0;i<50;i++) { 
            ptr[i]=new double[50000000]; // may throw exception 
            cout<<"Allocated 50000000 doubles in ptr["<<i<<"]\n"; 
        } 
    } 
    catch (bad_alloc &memoryAllocationException)// handle exception 
    { 
        cerr<<"Exception occurred: "<<memoryAllocationException.what()<<endl; 
    } 
    cout<<"Exception handled."<<endl; 
    return 0;
}

应用实例

为类模板Array重新定义下标操作符[],如果索引值越界,它会抛出一个out_of_range类型的异常

template<typename elemType>
class Array { 
public: 
    Array(int sz=DefaultArraySize) { 
        size=sz; 
        ia=new elemType[size];
    } 
    ~Array() {delete []ia;} 
    elemType & operator[](int ix) const { //下标运算符[ ]重载 
        if(ix<0||ix>=size) { //增加异常抛出,防止索引值越界 
            string eObj="out_of_range error in Array< elemType >::operator[]()"; 
            throw out_of_range(eObj); 
        } 
        return ia[ix]; 
    } 
private: 
    int size; 
    elemType * ia; 
};

int main()
{ 
    int i; 
    Array<int> arr; 
    try { 
        for (i=0;i<=DefaultArraySize;i++) { 
            arr[i]=i+1;   //写入ia[10]时出界 
            cout<<setw(5)<<arr[i];            
        } 
        cout<<endl; 
    } 
    catch(const out_of_range & excp)   {
        cerr<<'\n'<<excp.what()<<'\n'; //打印"out_of_range error in array<elemType>::operator[]()" 
        return -1;    
    } 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值