2015-05-19 98 views
3

假設我有一個C++庫。圖書館在某些情況下會拋出異常。我想讓這個庫的用戶能夠簡單地捕獲這些異常並告訴出了什麼問題。處理它的最好方法是什麼?我提出了以下可能的解決方案:我應該如何實現我的C++異常?

  1. 只需使用帶有自定義錯誤消息的標準異常類即可。然而,這會讓那些發現異常情況的人說出什麼是錯的,這令人討厭。
  2. 創建一個std::exception的子類並拋出它。添加某種錯誤代碼(可能是一個枚舉?或一個宏?),以便用戶可以檢查出錯的地方。
  3. 創建多個異常子類:對於每個可能的場合,都會引發一個異常。這似乎是一個很好的想法,但我認爲爲每一個可能的錯誤創建一個子類太過分了。

我真的不能決定。什麼是正確的方式去做這件事?

+0

異常層次結構是可維護的C++程序和庫的基礎。這個問題的答案包含有關接受的最佳實踐的重要信息。這個問題不應該被擱置。 –

回答

2

由於程序寫入不正確而發生的異常應該從std::logic_error派生。這方面的例子是超出界限的索引。 A std::logic_error是您預期不會發生的錯誤,大多數程序將無法從中恢復。

應該從std::runtime_error導出可恢復的異常(即由於資源不可用而不能完成的)。

類型的異常應該解釋出了什麼問題。任何可能對開發人員感興趣的輔助信息都可以在what()字符串中找到。如果您想向開發者提供一連串因果關係,請考慮使用std::throw_with_nested。這使得感興趣的開發人員可以在不需要通過源代碼的情況下發現他爲什麼操作失敗。

考慮使用例外層次結構。這可以讓您的班級的消費者輕鬆編寫一般性故障條件,同時允許他對重要的個別故障進行測試。

一個人爲的例子:

struct failed_to_start : std::runtime_error { 
    using std::runtime_error::runtime_error; 
}; 

struct engine_fault : failed_to_start { 
    using std::failed_to_start::failed_to_start; 
}; 

struct engine_flooded : engine_fault { 
    using std::engine_fault::engine_fault; 
}; 

struct wrong_key : std::logic_error { 
    using std::logic_error::logic_error; 
}; 

編輯: 由請求,使用throw_with_nested的全工作實施例(和其他一些有用的技術)

#include <iostream> 
#include <string> 
#include <stdexcept> 


enum class key { 
    none, mine, yours 
}; 

struct out_of_fuel : std::runtime_error { 
    using std::runtime_error::runtime_error; 
}; 

struct no_key : std::runtime_error { 
    using std::runtime_error::runtime_error; 
}; 

struct start_failure : std::runtime_error { 
    using std::runtime_error::runtime_error; 
}; 

struct wrong_key_error : std::logic_error { 
    using std::logic_error::logic_error; 
}; 

struct car_configuration_error : std::logic_error { 
    using std::logic_error::logic_error; 
}; 

struct fuel_tank { 
    fuel_tank(double initial) : _quantity { initial } {} 

    void remove_fuel(double amount) { 
     using namespace std; 
     if (amount > _quantity) { 
      throw out_of_fuel { "fuel tank has "s 
       + to_string(_quantity) 
       + " litres remaining, tried to remove "s 
       + to_string(amount) }; 
     } 
     _quantity -= amount; 
    } 

    double _quantity = 0.0; 
}; 

struct ignition { 
    ignition(key k) : _key_type { k } {} 

    void insert_key(key k) { 
     if (_key_type != k) { 
      throw wrong_key_error { "the wrong key was inserted" }; 
     } 
     _current_key = k; 
    } 

    void turn_key() { 
     if (_current_key != _key_type) { 
      throw no_key { "there is no key in the ignition" }; 
     } 
    } 

    key _current_key = key::none; 
    const key _key_type; 
}; 

struct engine { 
    void run() { 

    } 

}; 

struct car { 
    car(key k, double initial_fuel) 
    : _ignition(k) 
    , _fuel_tank(initial_fuel) 
    {} 

    void start(key k) 
    try 
    { 
     _ignition.insert_key(k); 
     _ignition.turn_key(); 
     _fuel_tank.remove_fuel(1); 
     _engine.run(); 
    } 
    catch(const std::logic_error& e) { 
     std::throw_with_nested(car_configuration_error { "car configuration error - please check your program" }); 
    } 
    catch(const std::exception& e) { 
     std::throw_with_nested(start_failure { "failed to start car" }); 
    } 

    ignition _ignition; 
    engine _engine; 
    fuel_tank _fuel_tank; 
}; 

void print_current_exception(int level = 0); 

void print_exception(const std::exception&e, const char* prefix, int level) 
{ 
    std::cerr << std::string(level, ' ') << prefix << ": " << e.what() << '\n'; 
    try { 
     std::rethrow_if_nested(e); 
    } 
    catch(const std::exception&) { 
     print_current_exception(level + 1); 
    } 
} 

void print_current_exception(int level) 
{ 
    auto eptr = std::current_exception(); 
    if (!eptr) 
     return; 

    try { 
     std::rethrow_exception(eptr); 
    } 
    catch(const std::logic_error& e) { 
     print_exception(e, "logic error", level); 
    } 
    catch(const std::runtime_error& e) { 
     print_exception(e, "runtime error", level); 
    } 
    catch(const std::exception& e) { 
     print_exception(e, "exception", level); 
    } 
} 

int main(int argc, const char * argv[]) 
{ 
    car my_car { key::mine, .05 }; 
    car your_car { key::yours, 100 }; 

    try { 
     my_car.start(key::mine); 
    } 
    catch(const std::exception&) { 
     print_current_exception(); 
    } 

    try { 
     your_car.start(key::mine); 
    } 
    catch(const std::exception&) { 
     print_current_exception(); 
    } 
    return 0; 
} 

預期輸出:

runtime error: failed to start car 
runtime error: fuel tank has 0.050000 litres remaining, tried to remove 1.000000 
logic error: car configuration error - please check your program 
logic error: the wrong key was inserted 
+0

感謝您的回答!你能詳細解釋一下'std :: throw_with_nested'嗎? – Venemo

+0

@Venemo答案更新了可編輯的例子。 –

6

3.創建多個異常子類:爲每個可能的場合引發一個異常。這似乎是一個很好的想法,但我認爲爲每一個可能的錯誤創建一個子類太過分了。

這,因爲你說的原因:你的用戶可以抓到他們想要捕捉的東西。

總之,使用例外,因爲它們是打算使用。

2.創建一個std::exception的子類並拋出它。添加某種錯誤代碼(可能是一個枚舉?或一個宏?),以便用戶可以檢查出錯的地方。

你可能希望在這種方法中,其中一個語義類型的異常有「子類別」或其他輔助數據,你從來沒有要過濾的扔,但可能是次要的興趣給您的用戶。