第65课 C++中的异常处理(下)

1、C++中的异常处理

    1.1、 catch语句块 可以抛出异常
        1.1.1、 catch中获捕的异常可以 被重新抛出
        1.1.2、 抛出的异常需要 外层的try-catch块来捕获
        1.1.3、 catch(…)块中抛异常的方法是 throw ;也是将 所有异常 重新抛出
                
    1.2、catch 块中 重新抛异常的意义
        1.2.1、 可以被外层try-catch块捕获,并 重新解释异常的信息
        1.2.2、 工程开发中使用这样的方式 统一异常类型
            A、 .假设我们的私有库使用到了 第3方的库函数 ,如func
            B、 .但 其抛出的异常类型为 int类型 ,很不友好。我们可以在私有库 使用func的地方捕获这个异常 ,并在catch块中 重新解释 这个异常 并抛出 为我们 自定义的Exception类型
    

/*************         异常的重新解释      **********/
#include <iostream>
using namespace std;
//演示在catch块中可以抛出异常
void Demo()
{
    try
    {
        try
        {
            throw 'c';
        }
        catch(int i)
        {   
            cout << "Inner::catch(int i)" << endl;
            throw i;  //重新抛出异常
        }
        /*
        catch(char c)
        {   
            cout << "Inner::catch(char c)" << endl;
            throw c;  //重新抛出异常
        }
        */
        catch(...)
        {
            cout << "Inner:catch(...)" << endl;
            throw;//抛出所有类型异常。
        }
    }
    catch(...)
    {
        cout << "Outer::catch(...)" << endl;
    }
}
/*
    假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码
    
    函数名: void func(int i)
    抛出异常的类型:int
                        -1 ==> 参数异常
                        -2 ==> 运行异常
                        -3 ==> 超时异常
*/
void func(int i)//第三方库函数
{
    if(i<0)
    {
        throw -1;
    }
    
    if(i>100)
    {
        throw -2;
    }
    
    if(i == 11)
    {
        throw -3;
    }
    
    //正常运行时
    cout << "Call func(int i) normal" << endl;
}
//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc(int i)//私有库中重新定义捕获的异常信息。
{
    try
    {
        func(i);    //根据func抛出来的异常进行判断。
    }
    catch(int i)
    {
        switch(i)
        {
        case -1:
            throw "Invalid Parameter";
            break;
        case -2:
            throw "Runtime Exception";//运行时间异常
            break;
        case -3:
            throw "Timeout Exception";
            break;
        }
    }
    
}
int main()
{
    //Demo();
    
    cout << endl;
    
    try
    {
        MyFunc(11);
    }
    catch(const char* cs)
    {
        cout << "Exception info: " << cs << endl;
    }
    
    return 0;
}
/*
    小结:
    重新抛出异常的意义:假如你们公司使用第三方的类库,而这个类库抛出的异常信息可能不便于理解,这个时候就要自定义抛出异常信息了,就要用到重新抛出的异常信息。
*/


2、自定义异常类类型

    2.1、 对于 类类型异常 的匹配依旧是 至上而下严格匹配
    2.2、 赋值兼容性原则 在异常匹配中 依然适用 ,因此一般而言:
        2.2.1、 匹配 子类异常 的catch放在 上部(子类兼容父类)
        2.2.2、 匹配 父类异常 的catch放在 下部 (否则如果放上面,则子类异常由于赋值兼容会被父类捕获)。
    2.3、 工程中会 定义一系列的异常类 ,每个类代表可能出现的一种异常类型。
    2.4、 代码复用 时可能 需要重解释不同的异常类 (如由于继承的层次关系,很可能父类的异常类在子类中会被重新解释)
    2.5、 在定义catch语句块时推荐 使用 引用 作为参数

/*************      类类型的异常     *************/
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Exception : public Base
{
private:
    int m_id;
    string m_desc;
public:
    Exception(int id, string desc)//构造函数
    {
        m_id = id;
        m_desc = desc;
    }
    
    int id()  const     //考虑const  Exception对象时,函数加const,const的引用必须调用const类型的函数。
    {
        return m_id;
    }
    
    string description() const
    {
        return m_desc;
    }
};
/*
    假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码
    
    函数名: void func(int i)
    抛出异常的类型:int
                        -1 ==> 参数异常
                        -2 ==> 运行异常
                        -3 ==> 超时异常
*/
void func(int i)//第三方库函数
{
    if(i<0)
    {
        throw -1;
    }
    
    if(i>100)
    {
        throw -2;
    }
    
    if(i == 11)
    {
        throw -3;
    }
    
    //正常运行时
    cout << "Call func(int i) normal" << endl;
}
//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc(int i)//私有库中重新定义捕获的异常信息。
{
    try
    {
        func(i);    //根据func抛出来的异常进行判断。
    }
    catch(int i)
    {
        switch(i)
        {
        case -1:
            throw Exception(-1, "Invalid Parameter");  //抛出的是对象。
            break;
        case -2:
            throw Exception(-2, "Runtime Exception");
            break;
        case -3:
            throw Exception(-3, "Timeout Exception");
            break;
        }
    }
    
}
int main()
{
    //const Exception & q = Exception (- 5 , "sadfdsaf" );
    //表示这个引用不会修改对象内部的东西。
    try
    {
        MyFunc(11);
    }
    catch(const Exception& e)  //注意使用引用,防止生成临时对象,调用拷贝构造函数。引用效率高。先写着,后来再去理解为什么是异常类的引用?(可能会被复制?)不会被修改的对象的引用需要加const
    {
        cout << "Exception inFo:  " << endl;
        cout << "     ID: " << e.id() << endl;
        cout << "     Description: " << e.description() << endl;
    }

    catch(const Base& e)//父类异常放下部,赋值兼容性原则。子类初始化父类对象。父类指针指向子类对象(多态,实际指向的是子类)。

    {
        cout << "catch(const Base& e)" << endl;
    }
    
    return 0;
}

3、C++标准库中提供的异常类族

    3.1、 标准库(#include <stdexcept>)中的异常都是从 exception类 派生的
    3.2、 exception类有 两个主要的分支
        3.2.1、 ①logic_error :常用于程序中的可避免的 逻辑错误
        3.2.2、 ②runtime_error :常用于程序中无法避免的 恶性错误
    3.3、 标准库中的异常。
            



#ifndef _ARRAY_H_
#define _ARRAY_H_
#include <stdexcept>    //引入异常类
using namespace std;
template <typename T, int N>
class Array
{
    T m_array[N];
public:
    int length() const;     //const对象来调用
    bool set(int index, T value);
    bool get(int index, T& value);
    T& operator[](int index);
    T operator[](int index) const;
    virtual ~Array();   //子类可以重写父类的函数  
};
template <typename T, int N>
int Array<T, N>::length() const
{
    return N;
}
template <typename T, int N>
bool Array<T, N>::set(int index, T value)
{
    bool ret = (0 <= index) && (index < N);
    
    if (ret)
    {
        m_array[index] = value;
    }
    
    return ret;
}
template <typename T, int N>
bool Array<T, N>::get(int index, T& value)
{
    bool ret = (index>=0) && (index < N);
    
    if(ret)
    {
        value = m_array[index];
    }
    
    return ret;
}
template < typename T, int N>
T& Array<T, N>::operator[](int index)
{   
    if((0 <= index) && (index < N))
    {
        return m_array[index];      //返回值为引用
    }
    else
    {
        //out_of_range是标准库中的异常类,抛出字符串对象
        throw out_of_range("T& Array<T, N>::operator[](int index)");   
    }
}
template < typename T, int N>
T Array<T, N>::operator[](int index) const
{    
    if((0 <= index) && (index < N))
    {
        return m_array[index];
    }
    else
    {
        //out_of_range是标准库中的异常类
        throw out_of_range("T Array<T, N>::operator[](int index) const");   
    }
}
template < typename T, int N>
Array<T, N>::~Array()
{    
}

#endif


#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
#include <stdexcept>
using namespace std;
template
< typename T >
class HeapArray
{
private:
    int m_length;
    T* m_pointer;
    
    HeapArray(int len);
    HeapArray(const HeapArray<T>& obj);
    bool construct();
public:
    static HeapArray<T>* NewInstance(int length);
    int length() const;
    bool get(int index, T& value);
    bool set(int index ,T value);
    T& operator [] (int index);
    T operator [] (int index) const;
    HeapArray<T>& self();
    const HeapArray<T>& self() const;
    ~HeapArray();
};

template < typename T >
HeapArray<T>::HeapArray(int len)
{
    m_length = len;
}

template < typename T >
bool HeapArray<T>::construct()    //申请堆数组。
{   // m_length表示申请了m_length个T类型的数组
    m_pointer = new T[m_length];
    
    return m_pointer != NULL; //注意逻辑 ==
}

template <typename T>
HeapArray<T>* HeapArray<T>::NewInstance(int length) //初始化长度。
{
    HeapArray<T>* ret = new HeapArray<T>(length);
    
    if( !(ret && ret->construct()) ) //调用开始构造,返回到m_pointer
    {
        delete ret;
        ret = 0;
    }
        
    return ret;
}
template
< typename T > int HeapArray<T>::length() const
{
    return m_length;
}

template < typename T >
bool HeapArray<T>::get(int index, T& value)
{
    bool ret = (0 <= index) && (index < length());
    
    if( ret )
    {
        value = m_pointer[index];
    }
    
    return ret;
}
template
< typename T >
bool HeapArray<T>::set(int index, T value)
{
    bool ret = (0 <= index) && (index < length());
    
    if( ret )
    {
        m_pointer[index] = value;
    }
    
    return ret;
}
template
< typename T >
T& HeapArray<T>::operator [] (int index)
{
    if( (0 <= index) && (index < length()) )
    {
        return m_pointer[index];
    }
    else
    {
        throw out_of_range("T& HeapArray<T>::operator [] (int index)");
    }
}
template
< typename T >
T HeapArray<T>::operator [] (int index) const
{
    if( (0 <= index) && (index < length()) )
    {
        return m_pointer[index];
    }
    else
    {
        throw out_of_range("T HeapArray<T>::operator [] (int index) const");
    }
}
template
< typename T >
HeapArray<T>& HeapArray<T>::self()
{
    return *this;
}
template
< typename T >
const HeapArray<T>& HeapArray<T>::self() const
{
    return *this;
}
template
< typename T >
HeapArray<T>::~HeapArray()
{
    delete[]m_pointer;
}
#endif


#include <iostream>
#include <string>
#include "Array.h"
#include "HeapArray.h"
using namespace std;
int main()
{
/*
    Array<double, 5> ad;
    
    for(int i=0; i<ad.length(); i++)
    {
        ad[i] = i * i;
    }
    
    for(int i=0; i<ad.length(); i++)
    {
        cout << ad[i] << endl;
    }
*/    
    cout << endl;
    
    HeapArray<char>* pai = HeapArray<char>::NewInstance(10);
    
    if( pai != NULL )
    {
                //使用类模板时要指定泛型。
        HeapArray<char>& ai = pai->self();
        
        for(int i=0; i<ai.length(); i++)
        {
            ai[i] = i + 'a';
        }
        
        for(int i=0; i<ai.length(); i++)
        {
            cout << ai[i] << endl;//按照ASCII码表排列。
        }
    }
    
    delete pai;
    
    return 0;
}


4、C++异常机制中没有提供finally(始终会执行的语句块)块解决方案

    4.1、 RAII技术 (Resource Aquisition Is Initialization, 资源获得即初始化 )
        4.1.1、 基本的思路 :通过一个 局部对象 来表现资源 ,于是局部对象的析构函数将会释放资源。即,将 资源封装成一个类 ,将资源的初始化封装在构造函数里,释放封装在析构函数里。 要使用资源的时候,就实例化一个局部对象
        4.1.2、 在抛出异常的时候 ,由于 局部对象脱离了作用域 自动调用析构函数 ,会保证资源被释放。
    4.2、 具体做法
        4.2.1、 直接使用局部变量。(局部作用域的问题)
        try{
       File f("xxx.ttt"); //使用局部对象
       //其他操作
            }  //异常发生时和正常情况下,文件资源都在这里被释放catch {
            //...
       }
        4.2.2、 将资源封装在一个类中(局部作用域的问题)

class Test{
public:
    File *file;//资源被封装起来
    Test() { file = new file(...); }//资源的初始化,
    ~Test() { delete file;}         //资源的释放
};

try{
   Test t; //使用局部对象
   //其他操作
}  //无论什么情况下,t在这里被释放,同时调用析构函数catch {
    //...
}

        4.2.3、使用智能指针(局部作用域的问题)

try
{
    std::auto_ptr<file> pfile = new file();
    //....
}   //无论什么情况,智能指针都在这里被释放
catch(...)
{
    //....
}


5、构造函数中抛出异常的问题

    5.1、问题:构造未完成,析构函数不被调用,可能造成内存泄漏,因此,一般不建议 在构造函数中抛出异常。

                /******       构造函数中抛出异常        ******/
#include <iostream>

using namespace std;

class Test
{
private:
    int* p;

public:
    Test()
    {
        cout<< "Test()" << endl;
        p= new int[5];
        
        throw 10; //抛出异常,构造函数非正常结束    
}
    
    ~Test()
    {
        cout <<"~Test()" << endl; delete[] p;
    }
};

int main()
{
    try
    {
        Test t; //t未完成构造就出异常,就跳到catch语句去,
                //注意t的作用域为try的{},因没被执行完,所以
               //还没调用析构函数就跳到catch作用域中—内存泄漏   
}
    catch (int e)
    {
        cout <<"Catch:"<<e << endl;    //可能会导致对象析构函数没有被调用,对象是半成品对象。
    }
    
    return 0;
}
/*输出结果
Test()
Catch:10
*/

    5.2、解决方案:
        A、 构造函数中捕获异常、做善后处理后重新抛出异常
#include <iostream>
using namespace std;
class Test
{
private:
    int* p1;
    int* p2;
public:
    Test()
    {
        int nStep = 0;
        cout<< "Test()" << endl;
        try
        {
            nStep = 1;
            p1 = new int[5];
                    
            if( p1 == NULL)
            {
                //delete[] p1;
                throw nStep; //制造异常            
            }
            
            nStep = 2;
            p2 = new int[10];
            if( p2 != NULL )
            {
                //delete p2;
                throw nStep; //制造异常              
            }   
        }
        catch(int n)
        {
            //构造函数中发生异常时,析构函数不会被调用,所以
            //要自己做善后处理!
            if(n==1)
               delete[] p1;  //第1步出错时,销毁p1堆空间
           
            if(n==2)
               delete[] p2;//第2步出错时,销毁p2\p1堆空间
   
            throw n; //继续抛出,通知调用者构造函数调用失败
        }
    }
    
    ~Test()
    {
        cout <<"~Test()" << endl; //构造函数执行成功的时候。
        delete[] p1;
        delete[] p2;
    }
};
int main()
{
    try
    {
        Test t; //Test的构造函数中可能发生异常
    }
    catch (int e)
    {
        cout <<"Catch:"<<e << endl;//构造函数中的第二步出了问题。
    }
    
    return 0;
}
        
B、方案二,二阶构造(可参考27课)

6、小结

    6.1、 catch语句块中 可以抛出异常
    6.2、 异常的类型 可以是自定义类类型
    6.3、 赋值兼容性原则 在异常匹配中依然适用(子类在前,父类在后)
    6.4、 标准库中的异常都是从 exception类派生 的。















































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值