針對 C++11 提供的 <system_error> 這個函式庫,Heresy 之前已經在 part 1 寫了 error_code 的基本使用,也在 part 2 寫了 error_condition 的東西。而在 part 2 的時候也有提到,由於 Heresy 在寫 part 1 的時候,其實對於 std::error_code 和 std::error_condtion 的關係有點誤解,導致有一些東西沒講到,所以這邊就先回到 std::error_code 的部分,做一些補充。
首先,之前在實作 error_code 的時候,總共實作了:
- enum class ErrorCode
- class ErrorCategory
- message()
- name()
- make_error_code()
- is_error_code_enum<Heresy::ErrorCode>
這樣的實作,在使用 error_code 的時候雖然是沒問題的,但是當要把使用 ErrorCode 的 error_code 和別的 error_condtion 做比較的時候,就會因為沒辦法把 ErrorCode 傳換成 error_condtion 而出現編譯錯誤。
所以,為了功能上的完整性,除了 make_error_code() 之外,也需要另外定義 make_error_condition(),讓 ErrorCode 可以傳換成 error_condtion。
std::error_condition make_error_condition(ErrorCode ec) { return std::error_condition(static_cast<int>(ec), ErrorCategory::get()); }
但是這邊也要注意,雖然定義了 make_error_condition(),但是不能把 is_error_condition_enum<> 針對 ErrorCode 做顯示特定化(explicit specialization)。
因為如果同時把 is_error_code_enum<> 和 is_error_condition_enum<> 都定義成 true_type 的話,系統會認為 ErrorCode 可以自動被轉型成 error_code 或 error_condtion,當要直接使用 ErrorCode 來對比的時候,反而會出現因為不知道要轉成哪一種,而無法編譯的錯誤。
如果要同時定義 error_code 和 error_condtion 的話,比較好的做法,應該是把兩個完全分開,各自定義自己的錯誤代碼與 error_category,並明確區分清楚哪個是 error_code、哪個是 error_condtion,這樣才比較不會出現問題。
所以,一個比較完整的 error_code 實作,應該是要包含下面的各項元素:
namespace Heresy { enum class ErrorCode{ }; class ErrorCategory : public std::error_category { public: std::string message(int c) const override; const char* name() const noexcept override; }; std::error_code make_error_code(ErrorCode ec); std::error_condition make_error_condition(ErrorCode ec); } namespace std { template <> struct is_error_code_enum<Heresy::ErrorCode> : true_type {}; }
而如果想要讓自己的 error_code 可以針對既有的 error_condtion 來做進一步的比對、處理的話,其實也可以另外再去實作 error_category 的:
bool equivalent(int ec, const error_condition& condition) const noexcept; error_condition default_error_condition(int c) const noexcept;
這兩個函式。
這個 equivalent() 是用來檢查 error_category 本身的 error_code 和其他來源的 error_condtion 是否等價時使用的;他在定義上會等價於:
default_error_condition(ec) == condition;
也救世會先把自己的錯誤代碼透過 default_error_condition() 產生 error_condtion 後,再來拿做比較。
所以,如果有這個需求的話,其實可以透過重新實作這兩個函式(一般應該是改其中一個)來做到符合自己需求的比對方法。
比如說,如果希望把自己的 ErrorCode::ErrorType1 視為和 std::errc::io_error 等價的話,那就只要在 ErrorCategory 中,加入:
std::error_condition default_error_condition(int c) const noexcept override { switch (static_cast<ErrorCode>(c)) { case ErrorCode::ErrorType1: return std::errc::io_error; default: return std::error_condition(c,*this); } }
就可以了。
而如果是更複雜的狀況,則就需要去重新實作 equivalent() 函式了。
補充的部分就先到這邊,再來則是實作時的狀況。
實際上在實作一個函式庫的時候,其實這邊大部分的東西,都不會出現在 header 檔裡面。
下面就是一個函式庫,同時包含 error_code 和 error_condtion 的 header 檔(Lib.h、GitHub)的例子:
#pragma once #include <system_error> namespace Heresy { // Error code enum class error { Success = 0, TooLarge, TooSmall, Invaild }; // Error condition enum class errc { OutOfRange = 1, InvaildInput }; #pragma region Functions of library bool testFunction(int i, std::error_code& ec); #pragma endregion #pragma region Functions to make error_code and error_condition std::error_code make_error_code(error ec); std::error_condition make_error_condition(error ec); std::error_code make_error_code(errc ec); std::error_condition make_error_condition(errc ec); #pragma endregion } namespace std { template <> struct is_error_code_enum<Heresy::error> : true_type {}; template <> struct is_error_condition_enum<Heresy::errc> : true_type {}; }
可以看到,這邊定義了 error 作為 error_code 的列舉型別,而 errc 則是 error_condtion 的。
對應的 error_category 實作,則是不需要出現在 header 檔中,也不需要讓使用者看到。
而 make_error_code()、make_error_condition() 以及 is_error_code_enum<>、is_error_condition_enum<> 則是需要在 header 中;如果想要隱藏的話,則是可以寫在另一個檔案、然後被這個 header 引入(例如另外寫一個 error.hpp)。
原始碼的部分由於內容比較多,這邊就不整個貼了,有興趣的話請參考 GitHub 上的檔案(連結)。
在使用的話,大致上則會像是(GitHub):
#include <iostream> #include "Lib.h" int main() { std::error_code ec; Heresy::testFunction(1, ec); if (ec) { if (ec == Heresy::errc::OutOfRange) std::cerr << "Out of range, require 4 - 128" << std::endl; else std::cerr << ec.message() << std::endl; } else { std::cout << "It works" << std::endl; } return 0; }
這個範例的三個檔案放在:https://github.com/KHeresy/misc/tree/master/std_error_code
針對 error_code 和 error_condtion 的內容大致上就到這了。老實說,內容已經超過本來預期的篇幅了。
接下來,Heresy 是打算把這個東西是著引進到正在開發的程式裡面,如果還有碰到什麼值得一寫的狀況,就再來追加吧~
- part 1:error_code
- part 2:error_condition
- part 3:補充與實作