2009-11-03 89 views
91

我見過至少有一個可靠的源代碼(我使用的一個C++類)推薦C++中應用程序特定的異常類應該從std::exception繼承。我不清楚這種方法的好處。我應該從std :: exception繼承嗎?

在C#中,從ApplicationException繼承的原因很明顯:您會得到一些有用的方法,屬性和構造函數,只需添加或覆蓋需要的內容即可。與std::exception似乎你得到的是一個what()方法來覆蓋,你可以創建自己。

那麼使用std::exception作爲我的特定於應用程序的異常類的基類有什麼好處?是否有任何理由不從std::exception繼承?

+0

你可能想看看這個:http://stackoverflow.com/questions/1605778/1605852#1605852 – sbi 2009-11-03 23:31:51

+2

雖然作爲一個側面說明無關的特定問題,* C++ *類你不需要永遠成爲可靠的良好實踐資源,只是出於自己的權利。 – 2013-05-28 15:25:00

回答

64

最主要的好處是,使用你的類的代碼並不需要知道你的確切類型throw它,但可以只是catchstd::exception

編輯:馬丁等人指出的那樣,你真正想要從<stdexcept>頭部聲明的std::exception子類的一個派生。

+20

沒有辦法將消息傳遞給std :: exception。 std :: runtime_error接受一個字符串,並從std :: exception派生。 – 2009-11-03 19:59:24

+14

您不應該將消息傳遞給異常類型構造函數(考慮消息需要進行本地化。)相反,定義一種異常類型,將錯誤語義分類,將異常對象中存儲的內容存儲在需要格式化用戶友好消息,然後在catch站點這樣做。 – Emil 2011-01-04 01:33:08

+0

@Emil,對,感謝您的輸入。 – 2011-01-04 14:54:34

9

爲什麼你可能想從std::exception繼承的原因是因爲它可以讓你把那根據該類捕獲的異常,即:

class myException : public std::exception { ... }; 
try { 
    ... 
    throw myException(); 
} 
catch (std::exception &theException) { 
    ... 
} 
5

Difference: std::runtime_error vs std::exception()

無論您應該從它繼承還是不由你決定。標準std::exception及其標準後代提出了一種可能的異常分層結構(劃分爲logic_error子層次和runtime_error子層次結構)和一個可能的異常對象接口。如果你喜歡它 - 使用它。如果由於某種原因你需要不同的東西 - 定義你自己的異常框架。

3

如果您所有可能的例外都來自std::exception,您的catch塊可以簡單地catch(std::exception & e),並確保捕獲所有內容。

捕獲到異常後,您可以使用該方法獲取更多信息。 C++不支持鴨子輸入,所以另一個帶what方法的類將需要不同的catch和不同的代碼來使用它。

+7

不要繼承std :: exception,然後捕獲(std :: exception e)。您需要捕獲對std :: exception&(或一個指針)的引用,否則您將切斷子類數據。 – jmucchiello 2009-11-03 19:32:19

+1

現在這是一個愚蠢的錯誤 - 當然我知道更好。 http://stackoverflow.com/questions/1095225/exception-slicing-is-this-due-to-generated-copy-constructor/1095233#1095233 – 2009-11-03 20:52:03

16

std::exception繼承的原因是它是例外的「標準」基類,所以對於團隊中的其他人員來說,例如期望基類std::exception是自然的。

如果您正在尋找方便,您可以繼承std::runtime_error,它提供std::string構造函數。

+2

從任何其他標準異常類型派生除了std :: exception可能不是一個好主意。問題在於它們來自std :: exception非虛擬的,這可能會導致涉及多重繼承的細微錯誤,其中catch(std :: exception&)可能由於轉換爲std :: exception而默默無法捕獲異常曖昧。 – Emil 2011-01-04 01:15:00

+2

我閱讀了關於這個主題的文章。從純粹的邏輯意義上來看似乎是合理的。但我從來沒有見過MH在野外例外。我也不會考慮在例外情況下使用MH,因此看起來像大錘可以解決一個不存在的問題。如果這是一個真正的問題,我相信我們會從標準委員會看到它的行動來解決這個明顯的缺陷。所以我的意見是,std :: runtime_error(和家庭)仍然是完全可以接受的例外拋出或派生自。 – 2011-01-04 11:55:51

+2

@Emil:除了'std :: exception'外,從其他標準異常中派生出來是一個_excellent_想法。你不應該做的是從多於一個繼承。 – 2013-09-06 23:14:08

3

由於語言已經拋出std :: exception,因此無論如何您都需要抓住它來提供體面的錯誤報告。你也可以用同樣的方法來處理你自己的所有意想不到的例外情況。此外,幾乎所有拋出異常的庫都會從std :: exception中派生出它們。

換句話說,它要麼

catch (...) {cout << "Unknown exception"; } 

catch (const std::exception &e) { cout << "unexpected exception " << e.what();} 

而第二個選項是肯定更好。

6

有一個遺留問題,你應該知道的是對象切片。當您編寫throw e;時,throw-expression將初始化一個名爲異常對象的臨時對象,該對象的類型是通過從throw的操作數的靜態類型中除去任何頂級cv限定符來確定的。這可能不是你所期待的。問題的例子,你可以找到here

這不是反對繼承的論點,它只是「必須知道」的信息。

+3

我認爲帶走是「扔e;」是邪惡的,而「扔」;沒問題。 – 2009-11-03 19:34:16

+2

是的,'扔;'沒問題,但是你不應該寫這樣的東西。 – 2009-11-03 19:39:51

+0

Java開發人員使用「throw e」來重新拋出特別痛苦。 – 2009-11-03 22:22:24

9

您應該從boost::exception繼承。它提供了許多更多功能和更好理解的方式來攜帶更多數據......當然,如果您不使用Boost,那麼請忽略此建議。

+5

但是請注意,boost :: exception本身不是從std :: exception派生的。即使從boost :: exception派生,也應該從std :: exception派生(作爲單獨的註釋,每當從異常類型派生時,都應該使用虛擬繼承)。 – Emil 2011-01-04 01:04:38

36

std::exception的問題在於沒有接受消息的構造函數(在標準兼容版本中)。

因此我更喜歡從std::runtime_error派生。這源於std::exception,但其構造函數允許您將C字符串或std::string傳遞給將返回的構造函數(作爲char const*),當調用what()時。

+9

這不是一個好主意,可以將用戶友好因爲這會將低級代碼與本地化功能以及其他功能耦合在一起。相反,應在異常對象中存儲所有相關信息,並讓catch站點根據異常類型及其攜帶的數據格式化一個用戶友好的消息。 – Emil 2011-01-04 01:26:18

+16

@ Emil:當然如果你是異常攜帶用戶可顯示的消息,但通常它們僅用於記錄目的。在投擲網站你沒有上下文建立用戶信息(因爲這可能是一個圖書館)。捕獲網站將有更多的上下文,並可以生成適當的味精。 – 2011-01-04 01:40:57

+3

我不知道你有什麼想法,即異常僅用於記錄目的。 :) – Emil 2013-09-09 03:17:36

12

我曾經參與過一個大型代碼庫的清理工作,在這個代碼庫中,前面的作者拋出了整數,HRESULTS,std :: string,char *,隨機類......各處不同的東西;只是命名一個類型,它可能被扔在某個地方。根本沒有共同的基礎課程。相信我,一旦我們認識到所有拋出的類型都有一個共同的基礎,我們可以捕獲並知道什麼都不會過去,那麼事情就會變得更加整潔。所以請你自己(以及那些將來需要維護你的代碼的人)幫忙,並且從一開始就這樣做。

9

是的,你應該從std::exception派生。

其他人已經回答說,std::exception有問題,您無法將文本消息傳遞給它,但通常不是在投擲點嘗試格式化用戶消息的好主意。相反,使用異常對象將所有相關信息傳輸到catch站點,然後可以格式化用戶友好的消息。

+6

+1,好點。無論從關注點分離還是國際視角來看,最好讓表示層構建用戶消息。 – 2010-02-26 13:55:24

+0

@JohnMGant和Emil:有趣的是,你能指出一個具體的例子來說明如何做到這一點。我明白,可以從'std :: exception'派生並攜帶異常的信息。但誰將負責構建/格式化錯誤消息? ':: what()'函數還是別的? – alfC 2014-06-25 20:42:54

+1

您將在catch站點格式化消息,很可能是在應用程序(而不是庫)級別。您可以按類型捕獲它,然後探測它的信息,然後本地化/格式化適當的用戶消息。 – Emil 2014-09-02 00:44:06

3

是否從任何標準異常類型派生是第一個問題。這樣做可以爲所有標準庫異常和您自己的異常處理程序啓用一個異常處理程序,但它也鼓勵這種全部處理程序。問題是人們應該只捕捉一個人知道如何處理的異常。例如,在main()中,如果在退出之前將what()字符串記錄爲最後的手段,那麼捕獲所有std :: exceptions可能是件好事。但是,在其他地方,這不太可能是一個好主意。

一旦您決定是否從標準異常類型派生,那麼問題是應該以哪個爲基礎。如果您的應用程序不需要i18​​n,您可能會認爲在呼叫站點格式化消息與保存信息並在呼叫站點生成消息一樣好。問題是可能不需要格式化的消息。最好使用懶惰的消息生成方案 - 也許使用預先分配的內存。然後,如果需要消息,它將在訪問時生成(並且可能緩存在異常對象中)。因此,如果消息在被拋出時生成,那麼std :: exception派生,就像std :: runtime_error需要作爲基類一樣。如果消息是懶惰生成的,那麼std :: exception是合適的基礎。

0

在大型封裝系統上工作時,子類異常的另一個原因是更好的設計方面。您可以將其重用用於驗證消息,用戶查詢,致命控制器錯誤等。您可以簡單地將它「捕捉」在主源文件上,而不是重寫或重新鉤住所有驗證信息,而是將錯誤拋出到整個類集中的任何位置。

例如致命的異常會終止程序,驗證錯誤只會清除堆棧,用戶查詢會向最終用戶提出問題。

這樣做也意味着您可以重用相同的類,但在不同的接口上。例如一個Windows應用程序可以使用消息框,一個Web服務將顯示HTML並且報告系統將記錄它等等。

0

儘管這個問題是相當老已經回答了很多,我只想補充就怎麼做適當的異常處理在C++ 11的說明,因爲我不斷地失蹤這在有關異常的討論:

使用std::nested_exceptionstd::throw_with_nested

它是通過寫一個適當的異常處理程序在計算器上herehere,你怎麼能對你例外你的代碼中得到回溯,而不需要一個調試器或繁瑣的記錄,描述了將重新嵌套的excep蒸發散。

由於您可以對任何派生的異常類執行此操作,因此可以將大量信息添加到此類回溯中! 您還可以看看我的MWE on GitHub,其中回溯會是這個樣子:

Library API: Exception caught in function 'api_function' 
Backtrace: 
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed 
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt" 

你甚至不需要爲了得到足夠的信息,以繼承std::runtime_error時拋出一個異常。

我在子類中看到的唯一好處(而不是僅使用std::runtime_error)是您的異常處理程序可以捕獲您的自定義異常並執行一些特殊的操作。例如:

try 
{ 
    // something that may throw 
} 
catch(const MyException & ex) 
{ 
    // do something specialized with the 
    // additional info inside MyException 
} 
catch(const std::exception & ex) 
{ 
    std::cerr << ex.what() << std::endl; 
} 
catch(...) 
{ 
    std::cerr << "unknown exception!" << std::endl; 
}