<system_error>
From Boost.System documentation:
Error conditions originating from the operating system or other low-level application program interfaces (APIs) are typically reported via an integer representing an error code. When these low-level API calls are wrapped in portable code, such as in a portable library, some users want to deal with the error codes in portable ways. Other users need to get at the system specific error codes, so they can deal with system specific needs.
Goals
The library serves an implementation of specific error handling design. Its core goals are:
lightweight non-allocating error representation type, able to be passed by value but still offering some polymorphic behavior
ability to define custom error numbers and categories
ability to map custom error numbers to a generic category
support for converting error values to string messages
exception class supporting error representation type if there is a need to report via exception
Design
Each error category is (preferably immutable) global object that implements error category interface, which primary function is returning string representation for the given error condition.
-
The error representation type holds only 2 data members: error number (as integer) and a pointer to an instance of error category. There are 2 such types:
std::error_code
represents platform-dependent error codestd::error_condition
represents platform-independent error code (rarely used)
Error numbers must only be unique within the same category.
Error number
0
is a special value and must mean no error in every category.std::system_error
is a dedicated exception type to hold error information when an exception object is desired
Synopsis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
namespace std { enum class errc { /* constants defined as aliases of POSIX error numbers */ }; template <typename T> struct is_error_code_enum : false_type {}; template <typename T> struct is_error_condition_enum : false_type {}; // other specializations (for std::io_errc and std::future_errc) defined in other headers template <> struct is_error_condition_enum<errc> : true_type {}; class error_category { public: virtual ~error_category(); error_category& operator=(const error_category&) = delete; virtual const char* name() const noexcept = 0; virtual error_condition default_error_condition(int code) const noexcept { return error_condition(code, *this); } virtual bool equivalent(int code, const error_condition& condition) const noexcept { return default_error_condition(code) == condition; } virtual bool equivalent(const error_code& code, int condition) const noexcept { return *this == code.category() && code.value() == condition; } virtual string message(int condition) const = 0; // in C++11 operators: ==, !=, < // in C++20 operators: ==, <=>, rest synthesised strong_ordering operator<=>(const error_category& rhs) const noexcept; }; const error_category& generic_category() noexcept; // POSIX error codes const error_category& system_category() noexcept; // platform-specific error codes class error_code { public: error_code() noexcept : error_code(0, system_category()) {} error_code(int ec, const error_category& ecat) noexcept : ec(ec), ecat(&ecat) {} template < typename ErrorCodeEnum, typename enable_if<is_error_code_enum<ErrorCodeEnum>::value>::type* = nullptr > error_code(ErrorCodeEnum e) noexcept; template < typename ErrorCodeEnum, typename enable_if<is_error_code_enum<ErrorCodeEnum>::value>::type* = nullptr > error_code& operator=(ErrorCodeEnum e) noexcept; void assign(int ec, const error_category& ecat) noexcept { this->ec = ec; this->ecat = &ecat; } void clear() noexcept { *this = error_code(); } int value() const noexcept { return ec; } const error_category& category() const noexcept { return *ecat; } error_condition default_error_condition() const noexcept { return category().default_error_condition(value()); } string message() const { return category().message(value()); } explicit operator bool() const noexcept { return value() != 0; } private: int ec; const error_category* ecat; }; // in C++11 operators: ==, !=, < // in C++20 operators: ==, <=>, rest synthesised strong_ordering operator<=>(const error_code& lhs, const error_code& rhs) noexcept; template <typename CharT, typename Traits> basic_ostream<CharT, Traits>& operator<<(basic_ostream<CharT, Traits>& os, const error_code& ec) { return os << ec.category().name() << ':' << ec.value(); } class error_condition { // [...] analogically same as error_code but: // - has no default_error_condition // - has no operator<< }; error_code make_error_code(errc e) noexcept { return error_code(static_cast<int>(e), generic_category()); } error_condition make_error_condition(errc e) noexcept { return error_condition(static_cast<int>(e), generic_category()); } template <> struct hash<error_code> { size_t operator()(error_code ec) noexcept; } class system_error : public runtime_error { public: system_error(error_code ec, const string& what_arg); system_error(error_code ec, const char* what_arg); system_error(error_code ec); system_error(int ev, const error_category& ecat, const string& what_arg); system_error(int ev, const error_category& ecat, const char* what_arg); system_error(int ev, const error_category& ecat); const error_code& code() const noexcept; const char* what() const noexcept override; }; } // namespace std |
Usage
This design works best when:
an application needs to work with multiple subcomponents where each has its own error numbering system
the error passing mechanism must be lightweight
there are no dynamically generated error descriptions - each category has fixed numbers of errors with predetermined descriptions
Implementation
define your error numbers (enumeration or an integer type)
-
implement the category class
the class definition and its instance can be hidden from other code - you only need to expose a function that returns
const std::error_category&
override
thename
functionoverride
themessage
function(optional)
override
thedefault_error_condition
function if you wish to translate errors to a more generic category (such asstd::generic_category()
), by default the function does not change the category
(if error numbers are enumeration) specialize
std::is_error_code_enum
(optional) add
make_error_code
in the same namespace as the definition of error numberswhen reporting errors use
std::error_code
constructor or callmake_error_code
The last 3 steps can be repeated analogically for std::error_condition
.
Example
The program implements HTTP error numbers taking advantage of standard library features.
header:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <system_error> namespace lib { enum class http_error { no_error = 0, ok = 200, // ... bad_request = 400, forbidden = 403, not_found = 404, method_not_allowed = 405, // https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol im_a_teapot = 418, // ... }; const std::error_category& http_category() noexcept; // this function can be picked by ADL std::error_code make_error_code(http_error e) noexcept { return std::error_code(static_cast<int>(e), http_category()); } } // namespace lib namespace std { // this enables std::error_code constructor overload for enums template <> struct is_error_code_enum<lib::http_error> : std::true_type {}; } |
source:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <lib.hpp> #include <cerrno> namespace lib { namespace { int map_error_code(int http_code) noexcept { switch (http_code) { case 0: return 0; case 200: return 0; // ... case 403: return EACCES; case 404: return ENOENT; case 405: return ENOTSUP; // ... default: return EINVAL; // there is no better generic POSIX error } } class http_category_impl : public std::error_category { const char* name() const noexcept override { return "HTTP"; } // the goal of this function is to translate errors for specific category to a generic one std::error_condition default_error_condition(int code) const noexcept override { return std::error_condition(map_error_code(code), std::generic_category()); } std::string message(int condition) const override { switch (condition) { case 0: return "no error"; case 200: return "ok"; // ... case 403: return "forbidden"; case 404: return "not found"; case 405: return "method not allowed"; case 418: return "I'm a teapot"; // ... default: return "unknown error"; } } }; const http_category_impl http_category_instance; } const std::error_category& http_category() noexcept { return http_category_instance; } } |