1. 程式人生 > >C++的異常處理

C++的異常處理

自定義異常 測試結果 signed 程序員 去掉 程序 自然 中斷 做出

異常處理在C++中的地位是很尷尬的,他不被很多公司或者程序員認可,但是基於某些原因,個人依然覺得異常處理在C++程序中 是非常必要的。

一般來說,異常分為兩大類,一個是拋出異常,另一個是接受異常然後處理。

  • 拋出異常使用throw。
  • 接受異常 使用try....catch 語句塊。

1.標準異常拋出

一般來說,標準庫中類或者函數會在使用錯誤的時候拋出一些異常,這其實也被人詬病的,畢竟錯誤的使用庫是程序員的責任,標準庫不應該為此承擔。舉一個string的栗子:

try{
 std::string wrolen("234");
 std::string it = wrolen.substr(10);
}catch(std::exception& e){
    std::cout << "Something Wrong:"<<e.what() <<"\n";
}

這個例子將只有3個字符的wrolen,取其10個字符長度的子串,自然會發生錯誤。
結果為
what(): basic_string::substr: __pos (which is 10) > this->size() (which is 3)

使用try--catch後,程序不會立刻奔潰,而是執行打印語句,這說明在try語句中有string的異常被拋出。而我們剛有接住了這個異常。

我們來看完整的測試代碼

#include <iostream>
#include <string>

void doExceptionCatch()
{
    try{
        std::string wrolen("234");
        std::string it = wrolen.substr(10);
    }catch(std::exception &e){
        std::cout << "Something Wrong:"<<e.what() <<"\n";
    }
}

int main(int argc, char const *argv[])
{
  
    try{
        doExceptionCatch();
    }
    catch(std::out_of_range &e){
        std::cout << "In main handle Something Wrong:"<<e.what() <<"\n";
    }
    return 0;
}

測試結果
Something Wrong:basic_string::substr: __pos (which is 10) > this->size() (which is 3)

在doExceptionCatch函數中使用了catch後,本來的異常已經被處理了,因此,回到main函數後,沒有catch到任何異常,故沒有打印。

一個特意的改動是,在doExceptionCatch函數的catch中加一個throw關鍵字,於是這個異常會被繼續上拋,被main捕獲。

事實上,標準庫中定義了很多種異常,實際編碼中,你可以在你認為會產生異常的地方將它們拋出來。當然了,一定是確實產生了異常。

看下面的例子:

int returnInt(unsigned int val)
{
    return val > 2147483647 ? throw std::out_of_range("too big for a interger!"):val;
}

int main(int argc, char const *argv[])
{
    try{
        int intme = returnInt(2147483648);
        std::cout << "In main intme :"<<intme <<"\n";
    }catch(std::out_of_range &e){
        std::cout << "In main handle Something Wrong:"<<e.what() <<"\n";
    }
    return 0;
}

我們知道一個int的最大數值是2147483647,因此當用戶輸入的數比這個還大,那麽我們則拋出一個超出範圍的異常。這合情合理。當函數returnInt沒有拋出異常時,程序將正常執行,打印輸入的數字,否則不會執行打印,而是直接進入到catch語句塊。實現了類似(事實上也是)goto語句的功能。

2.自定義的異常拋出

標準庫當然不會具體到項目的方方面面,可能你還需要自行定義一些異常情況,比如網絡中斷的處理,文件無法打開的處理等,那麽你就可以自己定義異常了。一般來說,我們會繼承標準庫的std::exception來自定義異常類。

class MyException:public std::exception
{
public:
    //重載標準exception的what函數
    const char* what() const noexcept{
        return "Error for my Diy exception!";
    }

};
void throwMyexception() noexcept(false)//noexcept(false)表明該函數可以拋出異常,throw(MyException),在C++11中已經廢棄了這種寫法
{
    //這裏模擬發生的異常,然後拋出自定義異常
    if(!false){
        throw MyException();
    }
}
int main(int argc, char const *argv[])
{
    try{
        throwMyexception();
    }catch(MyException & e){
        std::cout << "my own exception is : "<< e.what() <<"\n";
    }
    return 0;
}

值得一提的是noexcept 關鍵字,該關鍵字在C++11中引入,用於標識是否會拋出異常。

3.異常捕獲

上面的例子中,已經涉及到如何捕獲異常了,這裏做個說明,不管是標準異常還是自定義的 異常,只要是可能存在異常拋出的位置,我們都可以使用try---catch語句塊取處理異常。

4.異常處理的時機

異常處理不是神丹妙藥,它不能也不應該作為你的軟件出錯後的救命稻草(如果軟件要奔潰,就該讓它早點奔潰),它應該作為軟件在不該發生某些問題下的補充處理,雖然與此同時,這樣的補充會引發代碼膨脹,甚至於濫用。

在《程序員的修煉》一書中,作者對異常的使用時機是這樣認為的,如果去掉異常處理的部分,程序和之前表現一致,則說明異常處理是正確的引入了,否則,如果一個原本異常的代碼,在異常處理後變正常了,則說明異常處理做了不該做的事。

故此,我的理解是,異常處理不應該試圖修復異常本身,而是應該將問題盡早暴露然後即時的對此做出處理,這個處理不是修復,修復應該是正常代碼該做的事。比如本文第一個例子中,代碼錯誤的取用了超出字符串長度的子串,這裏就不該使用異常處理(好吧,我給自己打臉了),更好的方案是先判斷字符串的長度,然後再取子串。第二個例子,用戶(調用者)錯誤的輸入了大於最大int型的數值,那麽可以使用異常,異常處理裏絕不是要把這個輸入值變得小一些,使之合法,而是忽視用戶的調用輸入,並給予提示。那麽為什麽不使用if語句直接判斷輸入值是否大於最大int呢?這就涉及到異常的代碼段的集中處理的優勢了,它能讓代碼看起來更緊湊,不然,如果這裏的判斷多了,if就該滿天飛了。
這篇文章對異常的好處做了很詳細的詮釋

話分兩頭,不使用異常也是可以的,但是觀察C++的語言發展,標準會更傾向於加入並優化異常的處理。除了極端的,對速度有超高要求的場景外,我們需要引入異常,以避免程序在毫無防備的地方奔潰退出,讓所有的異常都得到處理,就可以將軟件的健壯性提升,如果你的代碼寫得很爛,那就更該引入異常處理,這樣的話,奔潰時,程序至少能“死有所歸”。

C++的異常處理