Lab 15-2: Exception - returning reference

 
實習目標 練習使用 C++ 例外狀況 (exception) 的處理機制:
      當函式的回傳值有不可避免的功能時
      尋找較適當的例外處理地點
 
步驟一 有的時候函式的傳回值不可能拿來傳遞錯誤碼, 例如下面這個 Vector 類別的 int &operator[](int) 函式, 函式傳回所指定的元素的參考:
    class Vector
    {
    public:
        Vector(int size);
        ~Vector();
        int &operator[](int index);
    private:
        int *m_data;
        int m_size;
    };
    
    Vector::Vector(int size)
        : m_size(size), m_data(0)
    {
        if (m_size>0)
        {
            m_data = new int[m_size+1];
            for (int i=0; i<m_size; i++)
                m_data[i] = 0;
        }
        else
            m_size = 0;
    }
    
    Vector::~Vector()
    {
        delete[] m_data;
    }
    
    int& Vector::operator[](int index)
    {
        if ((index < m_size)&&(index >= 0))
            return m_data[index];
        else
            return m_data[m_size];
    }
這個函式的傳回值是 int& 型態的變數, 必須要 bind 在一個變數上, 如果發生傳入的 index 超出 (0, m_size) 範圍外的話, 如何通知呼叫的函式???

如下例:

    Vector iVector(4);
    ...
    iVector[2] = 10; // OK
    iVector[4] = 20; // Error index
雖然由於 operator[]() 傳回 m_data[m_size] 的參考, 使得程式不會出現 memory access violation 的錯誤, 但是接下去程式的邏輯應該不會正確地執行, 如何中斷這個程式的執行呢?
步驟二 請利用 throw 語法改寫 operator[]() 中 else 的部份, 在發現陣列 index 超過範圍時請 throw 一個自定的 array_index_out_of_bound 例外類別
    if ((index < m_size)&&(index>=0))
        return m_data[index];
    else
        throw array_index_out_of_bound(index, m_size);

這個 array_index_out_of_bound 例外類別請繼承標準 C++ 函式庫 stdexcept 中的 runtime_error 類別, 在建構時將非法存取的那個 i 值還有陣列的大小傳入建構元函式中, 並且合成一個錯誤訊息字串 (基本上在 C++ 裡有兩種方法你可以用來合成錯誤訊息,

1. 用 ostrstream, 例如:

    char buf[200];
    ostrstream os(buf, 199);
    os << "Try to access the " << index << "-th element" << '\0';
    // m_errorMsg = new char[...];
    // strcpy(m_errorMsg, buf);
2. 用 sprintf, 例如:
    char buf[200];
    sprintf(buf, "Try to access the %d-th element", index);
    // m_errorMsg = new char[...];
    // strcpy(m_errorMsg, buf);
), 記錄在 m_errorMsg 中, 類別可以宣告成:
    class array_index_out_of_bound: public runtime_error
    {
    public:
        array_index_out_of_bound(int index, int size);
        ~array_index_out_of_bound();
        const char *what() const;
    private:
        char *m_errorMsg;
    };
並且覆寫 exception::what() 成員函式, 傳回錯誤訊息
步驟三 測試程式如下, 特地把產生例外的程式放在 fun1() 函式內, 出錯時在 Vector::operator[] 內會 throw 一個 array_index_out_of_bound 的暫時性物件, fun1() 剩餘的迴圈會立即中斷回到 fun2() 函式中的 catch (array_index_out_of_bound &) 敘述, 由於它只處理部份的錯誤回復動作, 因此它繼續 throw 原來的 array_index_out_of_bound 例外物件, fun2() 函式內剩餘的部份不會執行, 流程回到 main() 函式中 catch (exception &) 敘述
    void fun1()
    {
        int i;
        Vector dataHolder(10);
        for (i=1; i<=10; i++)
            dataHolder[2*i] = 2*i;
        cout << "fun1()::after for loop\n";
    }
    
    void fun2()
    {
        int *ptr=new int[100];
        // ...
        try
        {
            fun1();
            ...
        }
        catch (array_index_out_of_bound &e)
        {
            cout << "fun2()::" << e.what() << endl;
            delete[] ptr;
            cout << "fun2()::" << "ptr memory released" << endl;
            throw;
        }
        cout << "fun2()::after catch\n";
        delete[] ptr;
    }

    void main()
    {
        try
        {
            fun2();
            ...
        }
        catch (exception &e)
        {
            cout << "main()::" << e.what() << endl;
        }
        cout << "main()::after catch\n";
    }
範例執行程式

輸出範例如下:

fun2()::array_index_out_of_bound::
       Try to access the 11-th element of a 10-element vector
fun2()::ptr memory released
main()::array_index_out_of_bound::
       Try to access the 11-th element of a 10-element vector
array_index_out_of_bound:: dtor()
main()::after catch
請注意在 fun1() 的 throw 敘述產生的暫時性物件, 一直到 main() 的 catch (exception &) 敘述結束時才會解構掉
步驟四 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .suo, .sdf, .filters, .users, debug\ 資料匣, 以及 ipch\ 資料匣下的所有內容) 壓縮起來, 選擇 Lab15-2 上傳, 後面的實習課程可能需要使用這裡所完成的程式
注意事項 這個實習主要的目的是讓你熟悉 C++ exception 的處理機制, 但是並不是告訴你撰寫一個動態管理記憶體的陣列物件時一定要用 exception 語法, 請不要誤會; 上課時我們談到 exception 語法所處理的例外狀況是執行時發生的錯誤, 是要給程式的使用者看到的, 不是給程式的設計者看的, 使用者不懂 C++ 的語法, 如果陣列的大小是由使用者指定的, 則不夠大使得存取發生錯誤的訊息可能就可以讓使用者看到, 可以要求使用者更改他的輸入, 如果陣列的大小是程式設計者設定的, 和使用者操作時輸入的參數無關, 那麼這個錯誤就不需要給使用者, 就應該用 assert 來設計, 代表程式設計者應該在開發的過程中就要處理好這個錯誤, 不要讓這個錯誤發生; 這和 exception 的語法沒有關係, 而是和你程式的設計有關。

C++ 物件導向程式設計課程 首頁

製作日期: 05/31/2004 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon