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

1. C++中的异常处理

(1)catch语句块可以抛出异常

  ①catch中获捕的异常可以被重新抛出

  ②抛出的异常需要外层的try-catch块来捕获

  ③catch(…)块中抛异常的方法是throw;也是将所有异常重新抛出

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

(2)catch块中重新抛异常的意义

  ①可以被外层try-catch块捕获,并重新解释异常的信息

  ②工程开发中使用这样的方式统一异常类型

    A.假设我们的私有库使用到了第3方的库函数,如func

    B.但其抛出的异常类型为int*类型,很不友好。我们可以在私有库使用func的地方捕获这个异常,并在catch块中重新解释这个异常并抛出为我们自定义的Exception类型.

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

【编程实验】异常的重新解释

#include <iostream>
using namespace std;

//演示在catch块中可以抛出异常
void Demo()
{
    try
    {
        try
        {
            throw 'c';
        }
        catch(int i)
        {
            cout << "Inner:catch(int i)" << endl;
            throw i;  //重新抛出异常
        }
        catch(...)
        {
            cout << "Inner:catch(...)" << endl;
            throw;  //抛出所有类型的异常
        }
    }catch(...)
    {
        cout << "Outer:catch(...)" << endl;
    }
}

/*
    假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码

    函数名: void func(int i)
    抛出异常的类型:int
                        -1 ==> 参数异常
                        -2 ==> 运行异常
                        -3 ==> 超时异常
*/
void func(int i)
{
     )
    {
        ;
    }

     )
    {
        ;
    }

    )
    {
        ;
    }

    //正常运时时
    cout << "Call func(int i)" << endl;
}

//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc( int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
        :
            throw "Invalid Parameter";
            break;
        :
            throw "Runtime Exception";
            break;
        :
            throw "Timeout Exception";
            break;
        }
    }
}

int main()
{
    Demo();

    cout << endl;

    try
    {
        MyFunc();
    }
    catch(const char* cs)
    {
        cout << "Exception info: " << cs << endl;
    }
    ;
};
/*输出结果:
Inner:catch(...)
Outer:catch(...)

Exception info: Timeout Exception
*/

2. 自定义异常类类型

(1)对于类类型异常的匹配依旧是至上而下严格匹配

(2)赋值兼容性原则在异常匹配中依然适用,因此一般而言:

  ①匹配子类异常的catch放在上部

  ②匹配父类异常的catch放在下部(否则如果放上面,则子类异常由于赋值兼容会被父类捕获)。

(3)工程中会定义一系列的异常类,每个类代表可能出现的一种异常类型。

(4)代码复用时可能需要重解释不同的异常类(如由于继承的层次关系,很可能父类的异常类在子类中会被重新解释)

(5)在定义catch语句块时推荐使用引用作为参数

【编程实验】类类型的异常

#include <iostream>
#include <string>

using namespace std;

class Base
{
};

class Exception: public Base
{
    int m_id;
    string m_desc;
public:
    Exception(int id, string desc)
    {
        m_id = id;
        m_desc = desc;
    }

    int id() const  //考虑const Exception对象时,函数加const
    {
        return m_id;
    }

    string description() const
    {
        return m_desc;
    }
};

/*
    假设:当前的函数是第三方库中的函数。因此,我们无法修改源代码

    函数名: void func(int i)
    抛出异常的类型:int
                        -1 ==> 参数异常
                        -2 ==> 运行异常
                        -3 ==> 超时异常
*/
void func(int i)
{
     )
    {
        ;
    }

     )
    {
        ;
    }

    )
    {
        ;
    }

    //正常运时时
    cout << "Call func(int i)" << endl;
}

//以下是我们的私有库,当中使用到了第3方的库,这里需要将第3方库的
//异常类型统一成我们的异常信息格式
void MyFunc( int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
        :
            , "Invalid Parameter");
            break;
        :
            , "Runtime Exception");
            break;
        :
            , "Timeout Exception");
            break;
        }
    }
}

int main()
{
    try
    {
        MyFunc();
    }
    catch(const Exception& e) //注意使用引用以防止拷贝
    {
        cout << "Exception info: " << endl;
        cout << "     ID: " << e.id() << endl;
        cout << "     Description: " << e.description() << endl;
    }
    catch(const Base& e)  //父类异常放下部
    {
        cout << "catch(const Base& e)" << endl;
    }
    ;
};
/*输出结果:
Exception info:
     ID: -3
     Description: Timeout Exception
*/

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

(1)标准库(#include <stdexcept>)中的异常都是从exception类派生的

(2)exception类有两个主要的分支

  ①logic_error:常用于程序中的可避免的逻辑错误

  ②runtime_error:常用于程序中无法避免的恶性错误

(3)标准库中的异常

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

【编程实验】标准库中的异常使用

//Array.h

#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;
    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)
{
     <= 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)
{
     <= index) && (index < N);

    if (ret)
    {
        value = m_array[index];
    }

    return ret;
}

template < typename T, int N>
T& Array<T, N>::operator[](int index)
{
     <= 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
{
     <= 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

//HeapArray.h

#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);
    static void ReleaseInstance(const HeapArray<T>* pointer);
    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();           //与上面函数构成重载关系
    ~HeapArray();
};

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

template <typename T>
HeapArray<T>::HeapArray(const HeapArray<T>& obj)
{
    m_pointer = obj.m_pointer;
    m_length = obj.m_length;
}

template <typename T>
bool HeapArray<T>::construct()
{
    m_pointer = new T[m_length];

    return m_pointer != NULL;
}

template <typename T>
HeapArray<T>* HeapArray<T>::NewInstance(int len)
{
    HeapArray<T>* ret = new HeapArray<T>(len);

    if (!(ret && ret->construct()))
    {
        delete ret;
        ret = ;
    }

    return ret;
}

template <typename T>
void HeapArray<T>::ReleaseInstance(const HeapArray<T>* pointer)
{
    delete pointer;
}

template <typename T>
int HeapArray<T>::length() const
{
   return m_length;
}

template <typename T>
bool HeapArray<T>::get(int index, T& value)
{
     <= index)&&(index < m_length));

    if(ret)
    {
        value = m_pointer[index];
    }

    return ret;
}

template <typename T>
bool HeapArray<T>::set(int index, T value)
{
     <= index)&&(index < m_length));

    if(ret)
    {
        m_pointer[index] = value ;
    }

    return ret;
}

template <typename T>
T& HeapArray<T>::operator[](int index)
{
     <= index)&&(index < m_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
{
     <= index)&&(index < m_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() const
{
    return *this;
}

template <typename T>
HeapArray<T>& HeapArray<T>::self()
{
    return *this;
}

template <typename T>
HeapArray<T>::~HeapArray()
{
    delete[] m_pointer;
}

#endif

//test.cpp

#include <iostream>
#include <string>
#include <memory>  //for auto_ptr
#include "Array.h"
#include "HeapArray.h"
using namespace std;

void TestArray()
{
    Array<> a;

    ; i<a.length(); i++)
    {
        a[i] = i;
    }

    ; i<; i++)
    {
        cout << a[i] << endl;
    }
}

void TestHeapArray()
{
    //使用智能指针,目的是自动释放堆空间
    auto_ptr< HeapArray<));

    if(pa.get() != NULL)
    {
        HeapArray<double>& array = pa->self();

        ; i<array.length(); i++)
        {
            array[i] = i;
        }

        ; i<; i++)
        {
            cout << array[i] << endl;
        }
    }
}

int main()
{   //试验1:观察异常出错的信息
    TestArray();

    cout << endl;

    TestHeapArray();   

    //试验2: 观察异常出错的信息
    // try
    // {
       // TestArray();

       // cout << endl;

       // TestHeapArray();
    // }
    // catch(...)
    // {
       // cout << "Exception" << endl;
    // }

    ;
};

4. C++异常机制中没提供finally块解决方案

(1)RAII技术(Resource Aquisition Is Initialization,资源获得即初始化)

  ①基本的思路:通过一个局部对象来表现资源,于是局部对象的析构函数将会释放资源。即,将资源封装成一个类,将资源的初始化封装在构造函数里,释放封装在析构函数里。要使用资源的时候,就实例化一个局部对象

  ②在抛出异常的时候,由于局部对象脱离了作用域自动调用析构函数,会保证资源被释放。

(2)具体做法

  ①直接使用局部变量。

try{
   File f("xxx.ttt"); //使用局部对象
   //其他操作
}  //异常发生时和正常情况下,文件资源都在这里被释放
catch {
    //...
}

  ②将资源封装在一个类中

class Test{
public:
    File *file;//资源被封装起来
    Test() { file = new file(...); }
    ~Test() { delete file;}
};

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

  ③使用智能指针

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

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

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

【编程实验】构造函数中抛异常

#include <iostream>

using namespace std;

class Test
{
private:
    int* p;

public:
    Test()
    {
        cout<< "Test()" << endl;
        p= ];

        ; //抛出异常,构造函数非正常结束
    }

    ~Test()
    {
        cout <<"~Test()" << endl; delete[] p;
    }
};

int main()
{
    try
    {
        Test t; //t未完成构造就出异常,就跳到catch语句去,
                //注意t的作用域为try的{},因没被执行完,所以
               //还没调用析构函数就跳到catch作用域中—内存泄漏
    }
    catch (int e)
    {
        cout <<"Catch:"<<e << endl;
    }

    ;
}
/*输出结果
Test()
Catch:10
*/

  ②解决方案

【方案1】构造函数中捕获异常、做善后处理后重新抛出异常

#include <iostream>

using namespace std;

class Test
{
private:
    int* p1;
    int* p2;
public:
    Test()
    {
        ;
        cout<< "Test()" << endl;
        try
        {
            nStep = ;
            p1 = ];

            if( p1 == NULL)
            {
                //delete[] p1;
                throw nStep; //制造异常
            }

            nStep = ;
            p2 = ];

            if( p2 == NULL )
            {
                //delete p2;
                throw nStep; //制造异常
            }
        }
        catch(int n)
        {
            //构造函数中发生异常时,析构函数不会被调用,所以
            //要自己做善后处理!
            )
               delete[] p1;  //第1步出错时,销毁p1堆空间

            )
               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;
    }

    ;
}

【方案2】 二阶构造法(可参考《第27课 二阶构造模式》

6. 小结

(1)catch语句块中可以抛出异常

(2)异常的类型可以是自定义类类型

(3)赋值兼容性原则在异常匹配中依然适用

(4)标准库中的异常都是从exception类派生的。

上一篇:php function_name($type=0,$order_ids='',$flag=2)


下一篇:shuffle 过程