C++11 的錯誤碼標準 part 3:補充與實作

| | 0 Comments| 10:30
Categories:

針對 C++11 提供的 <system_error> 這個函式庫,Heresy 之前已經在 part 1 寫了 error_code 的基本使用,也在 part 2 寫了 error_condition 的東西。而在 part 2 的時候也有提到,由於 Heresy 在寫 part 1 的時候,其實對於 std::error_codestd::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 的時候雖然是沒問題的,但是當要把使用 ErrorCodeerror_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_codeerror_condtion,當要直接使用 ErrorCode 來對比的時候,反而會出現因為不知道要轉成哪一種,而無法編譯的錯誤。

如果要同時定義 error_codeerror_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_codeerror_condtion 的 header 檔(Lib.hGitHub)的例子:

#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_codeerror_condtion 的內容大致上就到這了。老實說,內容已經超過本來預期的篇幅了。

接下來,Heresy 是打算把這個東西是著引進到正在開發的程式裡面,如果還有碰到什麼值得一寫的狀況,就再來追加吧~


Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *