欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++?異常處理機制與自定義異常體系處理方式

 更新時間:2024年12月10日 10:06:10   作者:藤椒味的火腿腸真不錯  
本節(jié)將詳細介紹C++異常處理的相關(guān)概念、用法以及如何通過自定義異常體系來滿足程序的需求,同時,我們將對比C語言的傳統(tǒng)錯誤處理方式,分析C++異常機制的優(yōu)缺點,并探討標(biāo)準(zhǔn)庫中提供的異常體系,幫助開發(fā)者更好地理解和使用C++的異常處理功能,感興趣的朋友一起看看吧

 前言??????

在程序開發(fā)中,錯誤和異常的處理是至關(guān)重要的,它直接影響到程序的健壯性和穩(wěn)定性。C語言的錯誤處理主要依賴返回值和錯誤碼,雖然這種方式簡單直接,但在復(fù)雜的程序中,錯誤處理代碼往往難以維護且容易出錯。相比之下,C++引入的異常處理機制提供了一種更為高效和靈活的錯誤處理方式,使得程序的錯誤管理更加清晰和優(yōu)雅。

本節(jié)將詳細介紹C++異常處理的相關(guān)概念、用法以及如何通過自定義異常體系來滿足程序的需求。同時,我們將對比C語言的傳統(tǒng)錯誤處理方式,分析C++異常機制的優(yōu)缺點,并探討標(biāo)準(zhǔn)庫中提供的異常體系,幫助開發(fā)者更好地理解和使用C++的異常處理功能。

1.C語言傳統(tǒng)的處理錯誤的方式 ??

C語言傳統(tǒng)的錯誤處理機制主要有兩種方式:終止程序返回錯誤碼。這兩種方式雖然簡單易用,但各自也有其局限性和缺陷,尤其是在處理復(fù)雜錯誤或大規(guī)模程序時,往往會導(dǎo)致維護上的困難。 

1. 終止程序

一種常見的錯誤處理方式是直接終止程序,這種方式通常是通過使用assert宏實現(xiàn)的。assert會在程序運行時對條件進行檢查,如果條件不滿足,則程序會立即終止并輸出錯誤信息。

代碼如下:

#include <assert.h>
void foo(int x) {
    assert(x != 0);  // 如果x為0,程序終止
    printf("x is not zero\n");
}
int main() {
    foo(0);  // 觸發(fā)斷言,程序終止
    return 0;
}

缺陷

  • 用戶難以接受:程序直接終止,尤其是當(dāng)發(fā)生一些小錯誤或邊界條件時,用戶體驗很差。
  • 適用場景有限:這種方式適用于嚴重錯誤(如內(nèi)存錯誤、除0錯誤等),但不適合所有情況,因為大多數(shù)程序錯誤并不需要完全終止程序。

當(dāng)程序遇到無法恢復(fù)的錯誤時,assert可以有效地幫助開發(fā)者檢測出問題。但是,當(dāng)程序出現(xiàn)一些非致命錯誤時,用戶希望程序能夠優(yōu)雅地處理,而不是直接崩潰。 

2. 返回錯誤碼

另一個常見的錯誤處理方式是通過返回錯誤碼來通知程序出現(xiàn)了問題。這種方法在C語言中非常普遍,許多標(biāo)準(zhǔn)庫函數(shù)(如malloc、fopen)都通過返回一個特殊的錯誤碼來表示函數(shù)執(zhí)行失敗。開發(fā)者需要根據(jù)返回值來判斷錯誤,并做相應(yīng)的處理。

比如,malloc在分配內(nèi)存失敗時返回NULLfopen在打開文件失敗時返回NULL,errno則是一個全局變量,用于記錄最近一次系統(tǒng)調(diào)用的錯誤碼。

缺陷

  • 需要手動檢查錯誤碼:程序員必須檢查每個函數(shù)調(diào)用的返回值,以便發(fā)現(xiàn)錯誤。這導(dǎo)致了大量重復(fù)的錯誤處理代碼,增加了維護成本。
  • 錯誤信息不夠直觀:雖然可以通過errno來獲取詳細的錯誤信息,但這通常不如異常機制直觀。錯誤碼本身通常是數(shù)字,缺乏對錯誤本質(zhì)的描述,需要額外的邏輯去理解錯誤碼的含義。
  • 錯誤處理分散:程序中多處調(diào)用的函數(shù)可能會返回不同的錯誤碼,處理這些錯誤的邏輯往往分散在代碼的各個地方,導(dǎo)致代碼的可讀性差。

3.實際使用中的情況

在實際的C語言開發(fā)中,返回錯誤碼是最常見的錯誤處理方式。C語言沒有內(nèi)建的異常機制,所以程序員必須通過檢查每個函數(shù)調(diào)用的返回值來手動處理錯誤。對于一些簡單的錯誤,返回錯誤碼通常足夠。但對于較復(fù)雜的應(yīng)用程序,錯誤碼的使用可能變得冗長且難以維護。

在一些非常嚴重的錯誤情況下(如內(nèi)存分配失敗、文件操作失敗等),開發(fā)者有時會選擇直接終止程序。例如,在發(fā)現(xiàn)內(nèi)存分配失敗時,程序可能無法繼續(xù)執(zhí)行,這時直接通過exit()或其他方式終止程序可以避免進一步的錯誤。

2. C++異常概念??

C++的異常機制是一種專門用于處理錯誤特殊情況的機制,可以在程序運行時中斷當(dāng)前的控制流,并跳轉(zhuǎn)到一個可以處理該錯誤的代碼塊。這種機制使得程序能夠優(yōu)雅地應(yīng)對各種錯誤,而不是像C語言那樣依賴返回錯誤碼或直接終止程序。 

2.1 C++異常的基本概念

異常 (Exception)

  • 異常是指程序在運行時遇到的一種錯誤或意外情況,這種情況可能會導(dǎo)致程序無法正常繼續(xù)執(zhí)行。
  • 異常通過throw關(guān)鍵字拋出,表示錯誤已經(jīng)發(fā)生。
  • 異??梢允侨我獾腃++對象(如數(shù)字、字符串、自定義類對象),但通常建議使用標(biāo)準(zhǔn)庫中的異常類(如std::exception及其派生類)。

異常處理機制 C++通過三種關(guān)鍵字實現(xiàn)異常處理:

  • try:定義一個代碼塊,用于包含可能發(fā)生異常的代碼。
  • throw:在異常發(fā)生時,用于拋出異常。
  • catch:捕獲異常,并定義如何處理它。

2.2異常的拋出和匹配原則

1. 異常是通過拋出對象而引發(fā)的,該對象的類型決定了應(yīng)該激活哪個catch的處理代碼。

2. 被選中的處理代碼是調(diào)用鏈中與該對象類型匹配且離拋出異常位置最近的那一個

3. 拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個臨時對象,

所以會生成一個拷貝對象,這個拷貝的臨時對象會在被catch以后銷毀。(這里的處理類似

于函數(shù)的傳值返回)

4. catch(...)可以捕獲任意類型的異常,問題是不知道異常錯誤是什么。

5. 實際中拋出和捕獲的匹配原則有個例外,并不都是類型完全匹配,可以拋出的派生類對象,

使用基類捕獲,這個在實際中非常實用,我們后面會詳細講解這個。

在函數(shù)調(diào)用鏈中異常棧展開匹配原則

1. 首先檢查throw本身是否在try塊內(nèi)部,如果是再查找匹配的catch語句。如果有匹配的,則

調(diào)到catch的地方進行處理。

2. 沒有匹配的catch則退出當(dāng)前函數(shù)棧,繼續(xù)在調(diào)用函數(shù)的棧中進行查找匹配的catch。

3. 如果到達main函數(shù)的棧,依舊沒有匹配的,則終止程序。上述這個沿著調(diào)用鏈查找匹配的

catch子句的過程稱為棧展開。所以實際中我們最后都要加一個catch(...)捕獲任意類型的異

常,否則當(dāng)有異常沒捕獲,程序就會直接終止。

4. 找到匹配的catch子句并處理以后,會繼續(xù)沿著catch子句后面繼續(xù)執(zhí)行。

代碼案例:

double Division(int a, int b)
{
// 當(dāng)b == 0時拋出異常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg) {
cout << errmsg << endl;
}
catch(...){
cout<<"unkown exception"<<endl;
}
return 0;
}

這段代碼展示了如何在 C++ 中使用異常處理機制來管理錯誤(特別是除以零的錯誤)。通過使用 throwcatch,程序可以優(yōu)雅地處理異常,避免崩潰并提供友好的錯誤信息。雖然當(dāng)前的異常處理簡單直觀,但可以進一步改進,采用標(biāo)準(zhǔn)異常類和更多輸入驗證,以增強代碼的健壯性和可維護性。

2.3 異常的重新拋出

C++ 異常處理機制中的 異常重新拋出(rethrow)。當(dāng)捕獲到異常時,如果當(dāng)前函數(shù)無法處理異常,或希望更上層的函數(shù)處理異常,可以使用 throw; 語句重新拋出異常。這種機制允許在當(dāng)前 catch 塊中處理一些必要的操作(如資源釋放),然后將異常傳遞給調(diào)用鏈中的更外層函數(shù)進行進一步處理。 

代碼分析

1. Division 函數(shù)

該函數(shù)實現(xiàn)了兩個整數(shù)的除法操作:

  • 輸入:兩個整數(shù) ab。
  • 功能:如果 b == 0,拋出一個異常,提示 "除以零錯誤"。否則,執(zhí)行除法并返回結(jié)果。
double Division(int a, int b) {
    if (b == 0)  // 檢查除數(shù)是否為零
        throw "Division by zero condition!";  // 拋出異常
    return (double)a / (double)b;  // 返回除法結(jié)果
}

 2. Func 函數(shù)

該函數(shù)執(zhí)行除法操作并演示異常的重新拋出。主要有以下幾個步驟:

  • 輸入:從用戶讀取兩個整數(shù) lentime。
  • 功能:調(diào)用 Division 函數(shù)進行除法計算。如果發(fā)生除以零錯誤,則捕獲異常并執(zhí)行一些清理操作(如釋放動態(tài)分配的內(nèi)存)。

catch (...) 捕獲所有類型的異常。

delete[] array; 釋放在 Func 函數(shù)中動態(tài)分配的內(nèi)存。

然后,throw; 重新拋出異常,將其傳遞到 main 函數(shù)中的 catch 塊處理。

void Func() {
    int* array = new int[10];  // 動態(tài)分配內(nèi)存
    try {
        int len, time;
        cin >> len >> time;  // 從用戶輸入獲取兩個整數(shù)
        cout << Division(len, time) << endl;  // 調(diào)用Division進行除法操作
    } catch (...) {
        // 捕獲所有異常
        cout << "delete []" << array << endl;
        delete[] array;  // 釋放內(nèi)存
        throw;  // 重新拋出異常
    }
    // 在異常未發(fā)生時釋放內(nèi)存
    cout << "delete []" << array << endl;
    delete[] array;
}

 3. main 函數(shù)

main 函數(shù)包含了一個 try-catch 塊,負責(zé)捕獲從 Func 函數(shù)傳遞上來的異常:

  • 捕獲 const char* 類型的異常(即 Division by zero condition! 異常)。
  • 打印異常信息。
int main() {
    try {
        Func();  // 調(diào)用Func執(zhí)行操作
    } catch (const char* errmsg) {  // 捕獲字符串類型的異常
        cout << errmsg << endl;  // 打印異常信息
    }
    return 0;
}

 工作流程與邏輯

  • 執(zhí)行 main 函數(shù),調(diào)用 Func()。
  • Func 中:
    • 從用戶輸入 lentime,調(diào)用 Division(len, time) 執(zhí)行除法。
    • 如果 time == 0Division 會拋出 "Division by zero condition!" 異常。
    • catch (...) 捕獲該異常,并執(zhí)行異常處理:
    • 打印正在釋放的動態(tài)數(shù)組 array。
    • 釋放內(nèi)存(delete[] array)。
    • 使用 throw; 重新拋出異常,將其交給 main 函數(shù)的 catch 塊處理。
    • main 中:
    • 捕獲到異常 "Division by zero condition!",并打印該錯誤信息。

異常的重新拋出

throw; 的作用

  • 重新拋出捕獲的異常throw; 可以將當(dāng)前捕獲的異常重新拋出,并將其傳遞給調(diào)用鏈中的更外層 catch 塊進行處理。這使得異??梢员簧蠈雍瘮?shù)進一步處理。
  • 異常傳遞:在 catch 中進行一些必要的處理(如資源釋放、日志記錄等)后,可以選擇將異常交給外層的 catch 塊進行后續(xù)處理。

重新拋出場景

  • 資源清理:在 catch 中執(zhí)行資源釋放、日志記錄等操作后,再將異常傳遞給更外層的函數(shù)處理。
  • 中斷異常傳播:有時候,catch 只需要處理一些臨時的錯誤或簡單的資源清理工作,而不關(guān)心異常的最終處理,使用 throw; 可以讓更外層的 catch 塊進行實際的錯誤處理。
  • 分層處理:復(fù)雜的錯誤處理可以分層執(zhí)行。內(nèi)層函數(shù)先進行一些清理工作,而具體的錯誤處理和恢復(fù)操作交給外層函數(shù)。

總結(jié)

  • 異常重新拋出throw;)是 C++ 異常處理機制中的一種非常有用的功能。它允許捕獲的異常在某些情況下繼續(xù)傳遞到上層函數(shù),讓更上層的代碼處理實際的錯誤。
  • 在本例中,catch 塊處理了一些臨時的資源清理工作,然后使用 throw; 將異常重新拋出,最終由 main 函數(shù)中的 catch 塊捕獲并輸出異常信息。
  • 異常處理中的資源管理(例如內(nèi)存釋放)非常重要,因為它確保即使發(fā)生異常,動態(tài)分配的資源也能夠正確釋放,避免內(nèi)存泄漏等問題。

這種機制可以使異常處理更具靈活性,適應(yīng)不同層級的錯誤處理需求,同時保證資源的有效釋放。

2.4 異常安全

在 C++ 中,異常安全指的是在出現(xiàn)異常時,確保程序資源管理正確、數(shù)據(jù)一致性不被破壞。構(gòu)造函數(shù)和析構(gòu)函數(shù)是異常安全的關(guān)鍵點:

  • 構(gòu)造函數(shù):避免在構(gòu)造函數(shù)中拋出異常,否則對象可能不完全初始化。使用臨時對象或函數(shù)來避免復(fù)雜操作中出現(xiàn)異常。
  • 析構(gòu)函數(shù):盡量不要在析構(gòu)函數(shù)中拋出異常,因為這可能導(dǎo)致資源泄漏或程序崩潰。如果必須處理異常,捕獲并避免傳播。
  • 資源管理中的異常:在動態(tài)內(nèi)存分配(new)和鎖管理中,異常可能導(dǎo)致內(nèi)存泄漏或死鎖。
  • AII(資源獲取即初始化)模式能自動管理資源,確保異常發(fā)生時正確釋放資源。
  • 智能指針:使用智能指針(如 std::unique_ptr)可以自動管理內(nèi)存,避免內(nèi)存泄漏。
  • RAII 和智能指針是解決異常安全問題的重要工具,它們通過在對象生命周期內(nèi)自動管理資源來確保代碼的穩(wěn)定性。

2.5 異常規(guī)范

1. 異常規(guī)格說明的目的是為了讓函數(shù)使用者知道該函數(shù)可能拋出的異常有哪些。 可以在函數(shù)的

后面接throw(類型),列出這個函數(shù)可能拋擲的所有異常類型。

2. 函數(shù)的后面接throw(),表示函數(shù)不拋異常。

3. 若無異常接口聲明,則此函數(shù)可以拋擲任何類型的異常。 

// 這里表示這個函數(shù)會拋出A/B/C/D中的某種類型的異常
void fun() throw(A,B,C,D);
// 這里表示這個函數(shù)只會拋出bad_alloc的異常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 這里表示這個函數(shù)不會拋出異常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不會拋異常
thread() noexcept;
thread (thread&& x) noexcept;

3. 自定義異常體系 ????

在 C++ 中,自定義異常體系指的是定義自己的異常類,用于表示特定類型的錯誤或異常情況。這樣可以使異常處理更具可讀性和靈活性,方便區(qū)分不同類型的錯誤。

3.1  自定義異常類

自定義異常類通常繼承自標(biāo)準(zhǔn)庫中的 std::exception 類,或者更常見的是直接繼承自 std::runtime_errorstd::logic_error

基本步驟:

  • 繼承 std::exception 或其子類:自定義異常類應(yīng)繼承自 std::exceptionstd::runtime_error,并重寫構(gòu)造函數(shù)來傳遞錯誤消息。
  • 添加構(gòu)造函數(shù)和成員函數(shù):可以添加自定義的構(gòu)造函數(shù),用來初始化異常信息。
#include <iostream>
#include <stdexcept>
#include <string>
// 自定義異常類,繼承自 std::runtime_error
class MyException : public std::runtime_error {
public:
    MyException(const std::string& message) 
        : std::runtime_error(message) {}  // 調(diào)用基類構(gòu)造函數(shù)
};
void testFunction() {
    throw MyException("Something went wrong!");
}
int main() {
    try {
        testFunction();
    } catch (const MyException& e) {
        std::cout << "Caught MyException: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Caught std::exception: " << e.what() << std::endl;
    }
    return 0;
}

3.2  自定義多個異常類型

可以創(chuàng)建不同的異常類型來表示不同的錯誤情境。每種異常類型可以有不同的錯誤信息,或不同的邏輯處理

class FileNotFoundException : public std::runtime_error {
public:
    FileNotFoundException(const std::string& filename)
        : std::runtime_error("File not found: " + filename), filename(filename) {}
    const std::string& getFilename() const { return filename; }
private:
    std::string filename;
};
class InvalidInputException : public std::invalid_argument {
public:
    InvalidInputException(const std::string& message)
        : std::invalid_argument(message) {}
};
// 使用示例
try {
    throw FileNotFoundException("data.txt");
} catch (const FileNotFoundException& e) {
    std::cout << e.what() << " Filename: " << e.getFilename() << std::endl;
}

3.3. 捕獲自定義異常

try-catch 塊中捕獲自定義異常時,應(yīng)該使用適當(dāng)?shù)漠惓n愋筒东@,確保正確處理特定的錯誤。

try {
    throw InvalidInputException("Invalid input provided");
} catch (const InvalidInputException& e) {
    std::cout << "Caught InvalidInputException: " << e.what() << std::endl;
} catch (const std::exception& e) {
    std::cout << "Caught a generic exception: " << e.what() << std::endl;
}

3.4 異常層次結(jié)構(gòu)設(shè)計

  • 基類std::exceptionstd::runtime_error 用作基類,定義通用的接口(如 what())。
  • 子類:可以為每種具體錯誤類型創(chuàng)建子類,繼承基類并擴展功能。

這種設(shè)計方式使得異常體系更加清晰、可擴展。

總結(jié)

自定義異常體系的核心是通過繼承現(xiàn)有的異常類(如 std::exceptionstd::runtime_error)來創(chuàng)建符合特定需求的異常類型。這樣可以方便地進行異常區(qū)分,幫助編寫更具表現(xiàn)力和可維護性的代碼。

4.C++標(biāo)準(zhǔn)庫的異常體系 ????

C++ 提供了一系列標(biāo)準(zhǔn)的異常,定義在 中,我們可以在程序中使用這些標(biāo)準(zhǔn)的異常。它們是以父子類層次結(jié)構(gòu)組織起來的,如下所示:

說明:實際中我們可以可以去繼承exception類實現(xiàn)自己的異常類。但是實際中很多公司像上面一樣自己定義一套異常繼承體系。因為C++標(biāo)準(zhǔn)庫設(shè)計的不夠好用

int main()
{
try{
vector<int> v(10, 5);
// 這里如果系統(tǒng)內(nèi)存不夠也會拋異常
v.reserve(1000000000);
// 這里越界會拋異常
v.at(10) = 100;
}
catch (const exception& e) // 這里捕獲父類對象就可以
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}

5.異常的優(yōu)缺點????

C++異常的優(yōu)點:

1. 異常對象定義好了,相比錯誤碼的方式可以清晰準(zhǔn)確的展示出錯誤的各種信息,甚至可以包

含堆棧調(diào)用的信息,這樣可以幫助更好的定位程序的bug。

2. 返回錯誤碼的傳統(tǒng)方式有個很大的問題就是,在函數(shù)調(diào)用鏈中,深層的函數(shù)返回了錯誤,那

么我們得層層返回錯誤,最外層才能拿到錯誤,具體看下面的詳細解釋。

// 1.下面這段偽代碼我們可以看到ConnnectSql中出錯了,先返回給ServerStart,
ServerStart再返回給main函數(shù),main函數(shù)再針對問題處理具體的錯誤。
// 2.如果是異常體系,不管是ConnnectSql還是ServerStart及調(diào)用函數(shù)出錯,都不用檢查,因
為拋出的異常異常會直接跳到main函數(shù)中catch捕獲的地方,main函數(shù)直接處理錯誤。
int ConnnectSql()
{
// 用戶名密碼錯誤
if (...)
return 1;
// 權(quán)限不足
if (...)
return 2;
}
int ServerStart() {
if (int ret = ConnnectSql() < 0)
return ret;
int fd = socket()
if(fd < 0)
return errno;
}
int main()
{
if(ServerStart()<0)
...
return 0;
}

3. 很多的第三方庫都包含異常,比如boost、gtest、gmock等等常用的庫,那么我們使用它們

也需要使用異常。

4. 部分函數(shù)使用異常更好處理,比如構(gòu)造函數(shù)沒有返回值,不方便使用錯誤碼方式處理。比如

T& operator這樣的函數(shù),如果pos越界了只能使用異?;蛘呓K止程序處理,沒辦法通過返回

值表示錯誤。

C++異常的缺點:

1. 異常會導(dǎo)致程序的執(zhí)行流亂跳,并且非常的混亂,并且是運行時出錯拋異常就會亂跳。這會

導(dǎo)致我們跟蹤調(diào)試時以及分析程序時,比較困難。

2. 異常會有一些性能的開銷。當(dāng)然在現(xiàn)代硬件速度很快的情況下,這個影響基本忽略不計。

3. C++沒有垃圾回收機制,資源需要自己管理。有了異常非常容易導(dǎo)致內(nèi)存泄漏、死鎖等異常

安全問題。這個需要使用RAII來處理資源的管理問題。學(xué)習(xí)成本較高。

4. C++標(biāo)準(zhǔn)庫的異常體系定義得不好,導(dǎo)致大家各自定義各自的異常體系,非常的混亂。

5. 異常盡量規(guī)范使用,否則后果不堪設(shè)想,隨意拋異常,外層捕獲的用戶苦不堪言。所以異常

規(guī)范有兩點:

  • 一、拋出異常類型都繼承自一個基類。
  • 二、函數(shù)是否拋異常、拋什么異常,都使用 func() throw();的方式規(guī)范化。

總結(jié):異??傮w而言,利大于弊,所以工程中我們還是鼓勵使用異常的。另外OO的語言基本都是用異常處理錯誤,這也可以看出這是大勢所趨。

到此這篇關(guān)于C++ 異常處理機制與自定義異常體系的文章就介紹到這了,更多相關(guān)C++ 異常處理機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論