C++/C++11中std::runtime_error的使用
std::runtime_error:運行時錯誤異常類,只有在運行時才能檢測到的錯誤,繼承于std::exception,它的聲明在頭文件<stdexcept>中。std::runtime_error也用作幾個運行時錯誤異常的基類,包括std::range_error(生成的結果超出了有意義的值域范圍)、overflow_error(上溢)、underflow_error(下溢)、system_error(系統錯誤)。std::runtime_error類沒有默認構造函數,有兩個聲明為explicit的構造函數,一個接收參數為const char*類型,一個接收參數為const std::string&,這些實參負責提供關于錯誤的更多信息。std::runtime_error類還有一個繼承自std::exception類的what虛函數,返回關于異常的一些文本信息。
std::runtime_error is a more specialized class, descending from std::exception, intended to be thrown in case of various runtime errors. It has a dual purpose. It can be thrown by itself, or it can serve as a base class to various even more specialized types of runtime error exceptions, such as std::range_error,std::overflow_error etc. You can define your own exception classes descending from std::runtime_error, as well as you can define your own exception classes descending from std::exception.
 以下內容摘自:《C++Primer(Fifth Edition)》
異常是指在程序運行時發生的反常行為,這些行為超出了函數正常功能的范圍。典型的異常包括失去數據庫連接以及遇到意外輸入等。當程序的某部分檢測到一個它無法處理的問題時,需要用到異常處理。此時,檢測出問題的部分應該發出某種信號以表明程序遇到了故障,無法繼續下去了,而且信號的發出方無須知道故障將在何處得到解決。一旦發出異常信號,檢測出問題的部分也就完成了任務。
異常提供了一種轉移程序控制權的方式。C++異常處理涉及到三個關鍵字:try、catch、throw。關于這三個關鍵字的簡單使用可以參考: ?http://blog.csdn.net/fengbingchun/article/details/65939258
??????? 異常處理機制為程序中異常檢測和異常處理這兩部分的協作提供支持。在C++語言中,異常處理包括:
??????? (1)、throw表達式(throw expression):異常檢測部分使用throw表達式來表示它遇到了無法處理的問題。throw引發(raise)異常。throw表達式包含關鍵字throw和緊隨其后的一個表達式,其中表達式的類型就是拋出的異常類型。throw表達式后面通常緊跟一個分號,從而構成一條表達式語句。拋出異常將終止當前的函數,并把控制權轉移給能處理該異常的代碼。
??????? (2)、try語句塊(try block):異常處理部分使用try語句塊處理異常。try語句塊以關鍵字try開始,并以一個或多個catch子句(catch clause)結束。try語句塊中代碼拋出的異常通常會被某個catch子句處理。因為catch子句處理異常,所以它們也被稱作異常處理代碼(exception handler)。catch子句包括三部分:關鍵字catch、括號內一個(可能未命名的)對象的聲明(稱作異常聲明,exception declaration)以及一個塊。當選中了某個catch子句處理異常之后,執行與之對應的塊。catch一旦完成,程序跳轉到try語句塊最后一個catch子句之后的那條語句繼續執行。一如往常,try語句塊聲明的變量在塊外部無法訪問,特別是在catch子句內也無法訪問。如果一段程序沒有try語句塊且發生了異常,系統會調用terminate函數并終止當前程序的執行。
??????? (3)、一套異常類(exception class):用于在throw表達式和相關的catch子句之間傳遞異常的具體信息。
??????? 函數在尋找處理代碼的過程中退出:尋找處理代碼的過程與函數調用鏈剛好相反。當異常被拋出時,首先搜索拋出該異常的函數。如果沒有找到匹配的catch子句,終止該函數,并在調用該函數的函數中繼續尋找。如果還是沒有找到匹配的catch子句,這個新的函數也被終止,繼續搜索調用它的函數。以此類推,沿著程序的執行路徑逐層回退,直到找到適當類型的catch子句為止。如果最終還是沒能找到任何匹配的catch子句,程序轉到名為terminate的標準庫函數。該函數的行為與系統有關,一般情況下,執行該函數將導致程序非正常退出。
??????? 如果一段程序沒有try語句塊且發生了異常,系統會調用terminate函數并終止當前程序的執行。
??????? 那些在異常發生期間正確執行了”清理”工作的程序被稱作異常安全(exception safe)的代碼。編寫異常安全的代碼非常困難。
標準異常:C++標準庫定義了一組類,用于報告標準庫函數遇到的問題。這些異常類也可以在用戶編寫的程序中使用,它們分別定義在4個頭文件中:
(1)、exception頭文件定義了最通常的異常類exception,它只報告異常的發生,不提供任何額外的信息。
(2)、stdexcept頭文件定義了幾種常用的異常類,如下:
(3)、new頭文件定義了bad_alloc異常類型。
(4)、type_info頭文件定義了bad_cast異常類型。
標準庫異常類只定義了幾種運算,包括創建或拷貝異常類型的對象,以及為異常類型的對象賦值。我們只能以默認初始化的方式初始化exception、bad_alloc和bad_cast對象,不允許為這些對象提供初始值。其它異常類型的行為則恰恰相反:應該使用string對象或者C風格字符串初始化這些類型的對象,但是不允許使用默認初始化的方式。當創建此類對象時,必須提供初始值,該初始值含有錯誤相關的信息。
異常類型只定義了一個名為what的成員函數,該函數沒有任何參數,返回值是一個指向C風格字符串的const char*。該字符串的目的是提供關于異常的一些文本信息。what函數返回的C風格字符串的內容與異常對象的類型有關。如果異常類型有一個字符串初始值,則what返回該字符串。對于其它無初始值的異常類型來說,what返回的內容由編譯器決定。
異常處理(exception handling)機制允許程序中獨立開發的部分能夠在運行時就出現的問題進行通信并做出相應的處理。異常使得我們能夠將問題的檢測與解決過程分離開來。程序的一部分負責檢測問題的出現,然后解決該問題的任務傳遞給程序中的另一部分。檢測環節無須知道問題處理模塊的所有細節,反之亦然。
??????? 拋出異常:在C++語言中,我們通過拋出(throwing)一條表達式來引發(raised)一個異常。被拋出的表達式的類型以及當前的調用鏈共同決定了哪段處理代碼(handler)將被用來處理該異常。被選中的處理代碼是在調用鏈中與拋出對象類型匹配的最近的處理的代碼。其中,根據拋出對象的類型和內容,程序的異常拋出部分將會告知異常處理部分到底發生了什么錯誤。
??????? 當執行一個throw時,跟在throw后面的語句將不再被執行,throw語句的用法有點類似于return語句:它通常作為調節語句的一部分或者作為某個函數的最后(或者唯一)一條語句。相反,程序的控制權從throw轉移到與之匹配的catch模塊。該catch可能是同一個函數中的局部catch,也可能位于直接或間接調用了發生異常的函數的另一個函數中。
??????? 當拋出一個異常后,程序暫停當前函數的執行過程并立即開始尋找與異常匹配的catch子句。當throw出現在一個try語句塊內時,檢查與該try塊關聯的catch子句。如果找到了匹配的catch,就使用該catch處理異常。如果這一步沒找到匹配的catch且該try語句嵌套在其它try塊中,則繼續檢查與外層try匹配的catch子句。如果還是找不到匹配的catch,則退出當前的函數,在調用當前函數的外層函數中繼續尋找,依次類推。這一過程被稱為棧展開(stack unwinding)過程。棧展開過程沿著嵌套函數的調用鏈不斷查找,直到找到了與異常匹配的catch子句為止:或者也可能一直沒找到匹配的catch,則退出主函數后查找過程終止。
??????? 假設找到了一個匹配的catch子句,則程序進入該子句并執行其中的代碼。當執行完這個catch子句后,找到與try塊關聯的最后一個catch子句之后的點,并從這里繼續執行。如果沒找到匹配的catch子句,程序將退出。因為異常通常被認為是妨礙程序正常執行的事件,所以一旦引發了某個異常,就不能對它置之不理。當找不到匹配的catch時,程序將調用標準庫函數terminate,terminate負責終止程序的執行過程。
??????? Note:一個異常如果沒有被捕獲,則它將終止當前的程序。
??????? 棧展開過程中對象被自動銷毀:在棧展開過程中,位于調用鏈上的語句塊可能會提前退出。通常情況下,程序在這些塊中創建了一些局部對象。塊退出后它的局部對象也將隨之銷毀,這條規則對于棧展開過程同樣適用。如果在棧展開過程中退出了某個塊,編譯器將負責確保在這個塊中創建的對象能被正確地銷毀。如果某個局部對象的類型是類類型,則該對象的析構函數將被自動調用。與往常一樣,編譯器在銷毀內置類型的對象時不需要做任何事情。
??????? 析構函數與異常:析構函數總是會被執行的。出于棧展開可能使用析構函數的考慮,析構函數不應該拋出不能被它自身處理的異常。換句話說,如果析構函數需要執行某個可能拋出異常的操作,則該操作應該被放置在一個try語句塊當中,并且在析構函數內部得到處理。一旦在棧展開的過程中析構函數拋出了異常,并且析構函數自身沒能捕獲到異常,則程序將被終止。
??????? 異常對象(exception object):是一種特殊的對象,編譯器使用異常拋出表達式來對異常對象進行拷貝初始化。因此,throw語句中的表達式必須擁有完全類型。而且如果該表達式是類類型的話,則相應的類必須含有一個可訪問的析構函數和一個可訪問的拷貝或移動構造函數。如果該表達式是數組類型或函數類型,則表達式將被轉換成與之對應的指針類型。異常對象位于由編譯器管理的空間中,編譯器確保無論最終調用的是哪個catch子句都能訪問該空間。當異常處理完畢后,異常對象被銷毀。如果退出了某個塊,則同時釋放塊中局部對象使用的內存。因此,拋出一個指向局部對象的指針幾乎肯定是一種錯誤的行為。出于同樣的原因,從函數中返回指向局部對象的指針也是錯誤的。當我們拋出一條表達式時,該表達式的靜態編譯時類型決定了異常對象的類型。如果一條throw表達式解引用一個基類指針,而該指針實際指向的是派生類對象,則拋出的對象將被切掉一部分,只有基類部分被拋出。
??????? 捕獲異常:catch子句(catch clause)中的異常聲明(exception declaration)看起來像是只包含一個形參的函數形參列表。像在形參列表中一樣,如果catch無須訪問拋出的表達式的話,則我們可以忽略捕獲形參的名字。聲明的類型決定了處理代碼所能捕獲的異常類型.這個類型必須是完全類型,它可以是左值引用,但不能是右值引用。
??????? 當進入一個catch語句后,通過異常對象初始化異常聲明中的參數。和函數的參數類似,如果catch的參數類型是非引用類型,則該參數是異常對象的一個副本,在catch語句內改變參數實際上改變的是局部副本而非異常對象本身;相反,如果參數是引用類型,則和其它引用參數一樣,該參數是異常對象的一個別名,此時改變參數也就是改變異常對象。
??????? catch的參數還有一個特性也與函數的參數非常類似:如果catch的參數是基類類型,則我們可以使用其派生類類型的異常對象對其進行初始化。此時,如果catch的參數是非引用類型,則異常對象將被切掉一部分,這與將派生類對象以值傳遞的方式傳給一個普通函數差不多。另一方面,如果catch的參數是基類的引用,則該參數將以常規方式綁定到異常對象上。
??????? 異常聲明的靜態類型將決定catch語句所能執行的操作。如果catch的參數是基類類型,則catch無法使用派生類特有的任何成員。
??????? 通常情況下,如果catch接受的異常與某個繼承體系有關,則最好將該catch的參數定義成引用類型。
??????? 查找匹配的處理代碼:在搜索catch語句的過程中,我們最終找到的catch未必是異常的最佳匹配。相反,挑選出來的應該是第一個與異常匹配的catch語句。因此,越是專門的catch越應該置于整個catch列表的前端。因為catch語句是按照其出現的順序逐一進行匹配的,所以當程序使用具有繼承關系的多個異常時必須對catch語句的順序進行組織和管理,使得派生類異常的處理代碼出現在基類異常的處理代碼之前。
??????? 與實參和形參的匹配規則相比,異常和catch異常聲明的匹配規則受到更多限制。此時,絕大多數類型轉換都不被允許,除了一些極細小的差別之外,要求異常的類型和catch聲明的類型是精確匹配的:
(1)、允許在非常量向常量的類型轉換,也就是說,一條非常量對象的throw語句可以匹配一個接受常量引用的catch語句。
(2)、允許從派生類向基類的類型轉換。
(3)、數組被轉換成指向數組(元素)類型的指針,函數被轉換成指向該函數類型的指針。
除此之外,包括標準算術類型轉換和類類型轉換在內,其它所有轉換規則都不能在匹配catch的過程中使用。
如果在多個catch語句的類型之間存在著繼承關系,則我們應該把繼承鏈最低端的類(most derived type)放在前面,而將繼承鏈最頂端的類(least derived type)放在后面。
重新拋出:有時,一個單獨的catch語句不能完整地處理某個異常。在執行了某些校正操作之后,當前的catch可能會決定由調用鏈更上一層的函數接著處理異常。一條catch語句通過重新拋出(rethrowing)的操作將異常傳遞給另外一個catch語句。這里的重新拋出仍然是一條throw語句,只不過不包含任何表達式:throw;
空的throw語句只能出現在catch語句或catch語句直接或間接調用的函數之內。如果在處理代碼之外的區域遇到了空throw語句,編譯器將調用terminate。
一個重新拋出語句并不指定新的表達式,而是將當前的異常對象沿著調用鏈向上傳遞。
很多時候,catch語句會改變其參數的內容。如果在改變了參數的內容后catch語句重新拋出異常,則只有當catch異常聲明是引用類型時我們對參數所做的改變才會被保留并繼續傳播。
捕獲所有異常的處理代碼:為了一次性捕獲所有異常,我們使用省略號作為異常聲明,這樣的處理代碼稱為捕獲所有異常(catch-all)的處理代碼,形如catch(…)。一條捕獲所有異常的語句可以與任意類型的異常匹配。
catch(…)通常與重新拋出語句一起使用,其中catch執行當前局部能完成的工作,隨后重新拋出異常。
catch(…)既能單獨出現,也能與其它幾個catch語句一起出現。
如果catch(…)與其它幾個catch語句一起出現,則catch(…)必須在最后的位置。出現在捕獲所有異常語句后面的catch語句將永遠不會被匹配。
函數try語句塊與構造函數:通常情況下,程序執行的任何時刻都可能發生異常,特別是異常可能發生在處理構造函數初始值的過程中。構造函數在進入其函數體之前首先執行初始值列表。因為在初始值列表拋出異常時構造函數體內的try語句塊還未生效,所以構造函數體內的catch語句無法處理構造函數初始值列表拋出的異常。要想處理構造函數初始值拋出的異常,我們必須將構造函數寫出函數try語句塊(也稱為函數測試塊,function try block)的形式。函數try語句塊使得一組catch語句既能處理構造函數體(或析構函數體),也能處理構造函數的初始化過程(或析構函數的析構過程)。
在初始化構造函數的參數時也可能發生異常,這樣的異常不屬于函數try語句塊的一部分。函數try語句塊只能處理構造函數開始執行后發生的異常。和其它函數調用一樣,如果在參數初始化的過程中發生了異常,則該異常屬于調用表達式的一部分,并將在調用者所在的上下文中處理。
處理構造函數初始值異常的唯一方法是將構造函數寫成函數try語句塊。
noexcept異常說明:在C++11新標準中,我們可以通過提供noexcept說明(noexcept specification)指定某個函數不會拋出異常。其形式是關鍵字noexcept緊跟在函數的參數列表后面,用以標識該函數不會拋出異常。
對于一個函數來說,noexcept說明要么出現在該函數的所有聲明語句和定義語句中,要么一次也不出現。該說明應該在函數的尾置返回類型之前。我們也可以在函數指針的聲明和定義中指定noexcept。在typedef或類型別名中則不能出現noexcept。在成員函數中,noexcept說明符需要跟在const及引用限定符之后,而在final、override或虛函數的=0之前。
違反異常說明:如果一個函數在說明了noexcept的同時又含有throw語句或者調用了可能拋出異常的其它函數,編譯器將順利編譯通過,并不會因為這種違反異常說明的情況而報錯(不排除個別編譯器會對這種用法提出警告)。一旦一個noexcept函數拋出了異常,程序就會調用terminate以確保遵守不在運行時拋出異常的承諾。noexcept可以用在兩種情況下,一是我們確認函數不會拋出異常;二是我們根本不知道該如何處理異常。
通常情況下,編譯器不能也不必在編譯時驗證異常說明。
如果函數被設計為是throw()的,則意味著該函數將不會拋出異常:void f(int) throw();
異常說明的實參:noexcept說明符接受一個可選的實參,該實參必須能轉換為bool類型:如果實參是true,則函數不會拋出異常;如果實參是false,則函數可能拋出異常。
noexcept運算符:noexcept說明符的實參常常與noexcept運算符(noexcept orerator)混合使用。noexcept運算符是一個一元運算符,它的返回值是一個bool類型的右值常量表達式,用于表示給定的表達式是否會拋出異常。和sizeof類似,noexcept也不會求其運算對象的值。
noexcept有兩層含義:當跟在函數參數列表后面時它是異常說明符;而當作為noexcept異常說明的bool實參出現時,它是一個運算符。
異常說明與指針、虛函數和拷貝控制:盡管noexcept說明符不屬于函數類型的一部分,但是函數的異常說明仍然會影響函數的使用。函數指針及該指針所指的函數必須具有一致的異常說明。也就是說,如果我們為某個指針做了不拋出異常的說明,則該指針將只能指向不拋出異常的函數。相反,如果我們顯示或隱式地說明了指針可能拋出異常,則該指針可以指向任何函數,即使是承諾了不拋出異常的函數也可以。
如果一個虛函數承諾了它不會拋出異常,則后續派生出來的虛函數也必須做出同樣的承諾;與之相反,如果基類的虛函數允許拋出異常,則派生類的對應函數既可以允許拋出異常,也可以不允許拋出異常。
當編譯器合成拷貝控制成員時,同時也生成一個異常說明。如果對所有成員和基類的所有操作都承諾了不會拋出異常,則合成的成員是noexcept的。如果合成成員調用的任意一個函數可能拋出異常,則合成的成員是noexcept(false)。而且,如果我們定義了一個析構函數但是沒有為它提供異常說明,則編譯器將合成一個。合成的異常說明將與假設由編譯器為類合成析構函數時所得的異常說明一致。
異常類層次:標準庫異常類構成了下圖所示的繼承體系:
類型exception僅僅定義了拷貝構造函數、拷貝賦值運算符、一個虛析構函數和一個名為what的虛成員。其中what函數返回一個const char*,該指針指向一個以null結尾的字符數組,并且確保不會拋出任何異常。
類exception、bad_cast和bad_alloc定義了默認構造函數。類runtime_error和logic_error沒有默認構造函數,但是有一個可以接受C風格字符串或者標準庫string類型實參的構造函數,這些實參負責提供關于錯誤的更多信息。在這些類中,what負責返回用于初始化異常對象的信息。因為what是虛函數,所以當我們捕獲基類的引用時,對what函數的調用將執行與異常對象動態類型對應的版本。
實際的應用程序通常會自定義exception(或者exception的標準庫派生類)的派生類以擴展其繼承體系。這些面向應用的異常類表示了與應用相關的異常條件。和其它繼承體系一樣,異常類也可以看作按照層次關系組織的。層次越低,表示的異常情況就越特殊。例如,在異常類繼承體系中位于最頂層的通常是exception,exception表示的含義是某處出錯了,至于錯誤的細節則未作描述。
繼承體系的第二層將exception劃分為兩個大的類別:運行時錯誤和邏輯錯誤。運行時錯誤表示的是只有在程序運行時才能檢測到的錯誤;而邏輯錯誤一般指的是我們可以在程序代碼中發現的錯誤。
下面是從其他文章中copy的std::exception測試代碼,詳細內容介紹可以參考對應的reference:
#include "runtime_error.hpp"
#include <iostream>
#include <stdexcept>
#include <string>namespace runtime_error_ {
//
// reference: https://msdn.microsoft.com/en-us/library/tyahh3a9.aspx
int test_runtime_error_1()
{try {std::locale loc("test");} catch (std::exception& e) {std::cerr << "Caught " << e.what() << std::endl; // Caught bad locale namestd::cerr << "Type " << typeid(e).name() << std::endl; // Type class std::runtime_error};return 0;
}/
// reference: http://www.java2s.com/Tutorial/Cpp/0120__Exceptions/Throwyourownexceptionclassbasedonruntimeerror.htm
class DivideByZeroException : public std::runtime_error {
public:DivideByZeroException::DivideByZeroException() : runtime_error("attempted to divide by zero") {}
};static double quotient(int numerator, int denominator)
{throw DivideByZeroException(); // terminate functionreturn 0;
}int test_runtime_error_2()
{try {double result = quotient(1, 1);std::cout << "The quotient is: " << result << std::endl;} catch (DivideByZeroException& divideByZeroException) {std::cout << "Exception occurred: " << divideByZeroException.what() << std::endl; // Exception occurred: attempted to divide by zero}return 0;
}class CppBase_RunTime_Exception : public std::runtime_error {
public :CppBase_RunTime_Exception(int error_code_) : runtime_error(""), error_code(error_code_) {}CppBase_RunTime_Exception(int error_code_, const std::string& info_) : runtime_error(info_), error_code(error_code_) {}int get_error_code() const { return error_code; }
private:int error_code = 0;
};static int calc(int a)
{if (a > 0) {throw CppBase_RunTime_Exception(1, __FUNCTION__);}if (a < 0) {throw CppBase_RunTime_Exception(-1, __FUNCTION__);}throw CppBase_RunTime_Exception(0);return 0;
}int test_runtime_error_3()
{const int a{ 2 }, b{ -3 }, c{ 0 };try {calc(a);} catch (const CppBase_RunTime_Exception& e) {std::cerr << "error fun name: " << e.what() << ", error code: " << e.get_error_code() << std::endl;}try {calc(b);} catch (const CppBase_RunTime_Exception& e) {std::cerr << "error fun name: " << e.what() << ", error code: " << e.get_error_code() << std::endl;}try {calc(c);} catch (const CppBase_RunTime_Exception& e) {std::cerr << "error fun name: " << e.what() << ", error code: " << e.get_error_code() << std::endl;}std::cout << "over" << std::endl;return 0;
}} // namespace runtime_error_
GitHub:? https://github.com/fengbingchun/Messy_Test
總結
以上是生活随笔為你收集整理的C++/C++11中std::runtime_error的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: C++/C++11中std::excep
- 下一篇: 支持向量机(SVM)简介
