<system_error>
and error handling. On the one hand, it was written in 2010 and I simply may be considered necrophilic, but on the other hand, very little information on this topic in RuNet is available and many fairly recent articles refer to this cycle, which suggests that it does not lose its relevance to this day. day.
<system_error>
. It provides a set of tools for managing, suddenly, system errors.
class error_category
class error_code
class error_condition
class system_error
enum class errc
boost::system::
, not std::
.
system_category()
does not represent a Win32 error as it was intended. More on what this means will be said later.
<system_error>
is an integral part of the modern standard library]
<system_error>
, in a nutshell:
class error_category
- the base class, used to identify sources of errors, as well as categories of error codes and conditions.lass error_code
- represents the specific error value returned by the operation (for example, a system call).class error_condition
- something that you want to check and possibly respond to it in your code.class system_error
is the exception used to wrap error_code
when an error is passed using throw / catch.enum class errc
is a set of common error conditions inherited from POSIX.is_error_code_enum<>, is_error_condition_enum<>, make_error_code, make_error_condition
is a mechanism for converting enums with error values ​​to error_code
or error_condition
.generic_category()
- returns a category object used to classify error codes and conditions based on errc
.system_category()
- returns the category object used to [classify] error codes originating from the operating system.BeginXYZ/EndXYZ
. In my opinion, this design adds complexity and makes the API more error-prone.)
std::exception_ptr
from C ++ 11]
GetLastError()
.
getaddrinfo
family of functions uses its own set of error codes (EAI _...) on POSIX, but shares the “namespace” GetLastError()
in Windows. Programs that integrate other libraries (for SSL, regular expressions, and so on) will encounter other categories of error codes.
getaddrinfo
, SSL and regular expressions into one API should not force the user of this API to deal with the “explosion” of error code types. Adding a new source of errors to the implementation of this API should not change its interface.
error_code
vs error_condition
. Keep in touch.
lass error_code
- represents the specific error value returned by the operation (for example, a system call).class error_condition
- something that you want to check and possibly respond to it in your code.create_directory()
:
void create_directory ( const std::string & pathname, std::error_code & ec );
std::error_code ec; create_directory("/some/path", ec);
create_directory()
function returns control ec
will contain an error code specific to the OS. On the other hand, if the call was successful, then ec
will have a zero value. This is a tribute to tradition (used by errno and GetLastError()
), when zero indicates success, and any other values ​​indicate an error.
error_code
easily converted to bool
:
std::error_code ec; create_directory("/some/path", ec); if(!ec) { //Success. } else { //Failure. }
std::error_code ec; create_directory("/some/path", ec); if(ec.value() == EEXIST) //No! ...
ec
will contain a platform-specific error. On Windows, the error is likely to be ERROR_ALREADY_EXISTS
. (Worse, the code does not check the category of the error code, but we'll talk about this later.)
error_code::value()
, then you are doing something wrong.
EEXIST
error code ( EEXIST
or ERROR_ALREADY_EXISTS
) that you want to match with the [platform-independent] error condition (“directory already exists”). Yes, that's right, you need error_condition
.
error_code
and error_condition
objects (that is, when using the == operator or! = Operator):
error_code
and error_code
- the exact match is checked.error_condition
and error_condition
- an exact match is checked.error_code
and error_condition
- equivalence is checked.ec
error code ec
with the error_condition
object, which represents the “directory already exists” error. Just for this case, C ++ 0x provides std::errc::file_exists
. This means that you should write:
std::error_code ec; create_directory("/some/path", ec); if(std::errc::file_exists == ec) ...
EEXIST
or ERROR_ALREADY_EXISTS
and the error condition std::errc::file_exists
. Later I will show how you can add your own error codes and conditions with corresponding definitions of equivalence.
std::errc::file_exists
is one of the enum values ​​from the enum class errc
. For now, you should think of the enumerated values ​​of std::errc::*
as labels for error_condition
constants. In I will explain the next part how it works.)
error_condition
constants and the conditions under which equivalent error codes are generated.
error_code
class was proposed for TR2 as an auxiliary component in the file system libraries and network libraries. In that design, the error_code
constant was implemented so that it, whenever possible, corresponded to a platform-specific error. If a match is impossible or there are several matches, the library implementation converts a platform-specific error to standard error_code
.
generic_error
class was prototyped, but it did not suit me. A satisfactory solution was found when renaming generic_error
to error_condition
. In my experience, naming is one of the most difficult problems in the field of computer science, and choosing a good name is the main job.
enum class errc
work as a set of constants for error_condition
.
<system_error>
header file defines a class enum errc
as follows:
enum class errc { address_family_not_supported, address_in_use, ... value_too_large, wrong_protocol_type, };
error_condition
:
std::error_code ec; create_directory("/some/path", ec); if(std::errc::file_exists == ec) ...
errc
to error_condition
is used here using a single argument constructor. Simply. Right?
error_condition
it is also necessary to know the category of the error. The <system_error>
module uses categories to support multiple sources of errors. A category is an attribute for both error_code
and error_condition
.error_code
and error_condition
. Although the enum class errc
provides constants only for error_condition
, in other use cases, constants like error_code
may be required.error_code
or error_condition
should be supported. Ported programs may need to create error codes inherited from std::errc::*
. if(std::errc::file_exists == ec)
errc
to error_condition
, there are a few more steps.
template<class T> struct is_error_code_enum: public false_type {}; template<class T> struct is_error_condition_enum: public false_type {};
is_error_code_enum<>
, then it can be implicitly converted to error_code
. Similarly, if a type is registered using is_error_condition_enum<>
, it can be implicitly converted to error_condition
. By default, types are registered without conversion (hence the use of false_type
above), but the enum class errc
registered as follows:
template<> struct is_error_condition_enum<errc>: public true_type {};
class error_condition { ... //Only available if registered //using is_error_condition_enum<>. template<class ErrorConditionEnum> error_condition(ErrorConditionEnum e); ... }; class error_code { ... //Only available if registered //using is_error_code_enum<>. template<class ErrorCodeEnum> error_code(ErrorCodeEnum e); ... };
if(std::errc::file_exists == ec)
bool operator == ( const error_code & a, const error_code & b ); bool operator == ( const error_code & a, const error_condition & b );
error_condition
conversion constructor is available, but error_code
not.
error_condition
object contains two attributes: a value and a category. Now, when we got to the constructor, they need to be initialized correctly.
make_error_condition()
.
errc
is located in the std
, ADL finds make_error_condition()
in the same place.
make_error_condition()
is simple:
error_condition make_error_condition(errc e) { return error_condition ( static_cast<int>(e), generic_category() ); }
error_condition
constructor with two arguments to explicitly specify both the error value and the category.
error_code
conversion constructor (for a properly registered enumeration type), the called function would be make_error_code()
. The rest of the construction of error_code
and error_condition
same.
error_code
is primarily intended for use with platform-specific errors, portable code may want to create error_code
from an enumerated errc
value. For this reason, the [functions] make_error_code(errc)
and make_error_condition(errc)
. Portable code can use them as follows:
void do_foo(std::error_code & ec) { #if defined(_WIN32) //Windows implementation ... #elif defined(linux) //Linux implementation ... #else //do_foo not supported on this platform ec = make_error_code(std::errc::not_supported); #endif }
error_code
constants in <system_error>
were defined as objects:
extern error_code address_family_not_supported; extern error_code address_in_use; ... extern error_code value_too_large; extern error_code wrong_protocol_type;
constexpr
, but in the end it turned out to be incompatible with some other aspects of <system_error>
. Thus, only the conversion from the enumeration remained, since it was the best design available.
<system_error>
is extensibility.This means that you can use the mechanism just described to define your own error codes.
class enum
same one std::errc
:
enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 };
<system_error>
uses a convention in which zero means success.
enum
by dropping the keyword class
:
enum http_error { ... };
class enum
differs from the enum
fact that the first one encloses the names of the enumerated values ​​in the class scope [while the second one “throws” them into the global scope) . To access the enumeration values, you must specify the name of the class, for example http_error::ok
. You can emulate this behavior by wrapping the usual enum
namespace [ namespace
] :
namespace http_error { enum http_error_t { ... }; }
enum class
. Applying a namespace approach remains as an exercise for the reader.
enum class
but also prohibits implicit conversion of enumerated values ​​to other types]
error_code
consists of an error value and a category. The error category defines what the given enumeration value specifically means. For example, 100 may refer to both http_error::continue_request
, and std::errc::network_down
(ENETDOWN in Linux), and maybe something else.
error_category
:
class http_category_impl: public std::error_category { public: virtual const char * name() const; virtual std::string message(int ev) const; };
error_category
.
error_category::name()
must return a string identifying the category:
const char * http_category_impl::name() const { return "http"; }
std::ostream
. However, it would be desirable to make it unique within this program.
error_category::message()
converts the error value to the string describing it:
std::string http_category_impl::message(int ev) const { switch(ev) { case http_error::continue_request: return "Continue"; case http_error::switching_protocols: return "Switching protocols"; case http_error::ok: return "OK"; ... case http_error::gateway_timeout: return "Gateway time-out"; case http_error::version_not_supported: return "HTTP version not supported"; default: return "Unknown HTTP error"; } }
error_code::message()
, error_code
in turn, it calls the above virtual function to get an error message.
"HTTP version %d.%d not supported"
, the equivalent offline message will be "HTTP version not supported"
.
<system_error>
does not provide any help when it comes to localizing these messages. It is likely that messages originating from the error categories of the standard library will be based on the current locale. If your program requires localization, I recommend using the same approach.
error_category
is determined by its address. This means that when you write:
const std::error_category & cat1 = ...; const std::error_category & cat2 = ...; if(cat1 == cat2) ...
if
is evaluated as if you wrote:
if(&cat1 == &cat2) ...
const std::error_category & http_category();
http_category_impl http_category_instance; const std::error_category & http_category() { return http_category_instance; }
const std::error_category & http_category() { static http_category_impl instance; return instance; }
<system_error>
requires a function with a name make_error_code()
in order to associate the error value with a category. For HTTP errors, this function might look like this:
std::error_code make_error_code(http_error e) { return std::error_code ( static_cast<int>(e), http_category() ); }
error_condition
:
std::error_condition make_error_condition(http_error e) { return std::error_condition ( static_cast<int>(e), http_category() ); }
<system_error>
finds these functions using ADL, you must place them in the same namespace as the type http_error
.
http_error
can be used as constants error_code
, enable the transformation constructor using the template is_error_code_enum
:
namespace std { template<> struct is_error_code_enum<http_error>: public true_type {}; }
errc
. For example, the HTTP status code 403 Forbidden means the same as std::errc::permission_denied
.
error_category::default_error_condition()
allows you to define an error condition equivalent to a given error code. (The definition of equivalence was described in the second part.) For HTTP errors, you can write:
class http_category_impl: private std::error_category { public: ... virtual std::error_condition default_error_condition(int ev) const; }; ... std::error_condition http_category_impl::default_error_condition(int ev) const { switch(ev) { case http_error::forbidden: return std::errc::permission_denied; default: return std::error_condition(ev, *this); } }
error_condition
will have the same error and category as error_code
. This is the default behavior, as in the example shown above.
http_error
as constants error_code
, like when setting the error:
void server_side_http_handler ( ..., std::error_code & ec ) { ... ec = http_error::ok; }
std::error_code ec; load_resource("http://some/url", ec); if(http_error::ok == ec) ...
bool
will not work either]
error_code
directly from the response :
std::string load_resource ( const std::string & url, std::error_code & ec ) { //send request ... //receive response ... int response_code; parse_response(..., &response_code); ec.assign(response_code, http_category()); ... }
std::error_code ec; data = load_resource("http://some/url", ec); if(std::errc::permission_denied == ec) ...
http_error::forbidden
) is preserved, so no information is lost.
error_condition
.
<system_error>
not limited to error codes: error_condition
it can also be expanded.
error_code
and error_condition
:
lass error_code
- represents the specific error value returned by the operation (for example, a system call).class error_condition
- something that you want to check and possibly react to it in your code.getaddrinfo()
. : « , » « ». getaddrinfo()
:
EAI_AGAIN
EAI_NONAME
, . « » errno. , error_category
.WSAEAI_AGAIN
WSAEAI_NONAME
. POSIX, « » GetLastError()
. , std::system_category()
getaddrinfo()
.name_not_found_try_again
name_not_found
), API.open()
. errno ENOENT
, .
no_such_file_or_directory
. no_such_entry
, ENOENT
.not_enough_memory
resource_unavailable_try_again
too_many_files_open
too_many_files_open_in_system
low_system_resources
can be defined so that its equivalence is based on a combination of other error conditions. This will allow you to write checks as follows:
if(low_system_resources == ec) ...
error_condition
similar to the definition error_code
.
enum
for error values, similarly std::errc
:
enum class api_error { low_system_resources = 1, ... name_not_found, ... no_such_entry };
error_condition
consists of an error value and a category. To create a new category, you need to inherit a class from error_category
:
class api_category_impl: public std::error_category { public: virtual const char * name() const; virtual std::string message(int ev) const; virtual bool equivalent(const std::error_code & code, int condition) const; };
error_category::name()
must return a string identifying the category:
const char * api_category_impl::name() const { return "api"; }
error_category::message()
converts the error value to the string describing it:
std::string api_category_impl::message(int ev) const { switch(ev) { case api_error::low_system_resources: return "Low system resources"; .. } }
error_condition::message()
may not be likely. In this case, you can use the abbreviation and just write:
std::string api_category_impl::message(int ev) const { return "api error"; }
error_category::equivalent()
used to determine the equivalence of error codes and conditions. There are two overloads of this feature. First:
virtual bool equivalent(int code, const error_condition & condition) const;
error_code
in the current category and arbitrary error_condition
. Second overload:
virtual bool equivalent(const error_code & code, int condition) const;
error_condition
in the current category and arbitrary error_code
. Because you create error conditions, you need to override the second overload.
true
if you want it to error_code
be equivalent to your condition, otherwise return false
.
error_category::equivalent()
as follows:
bool api_category_impl::equivalent(const std::error_code & code, int condition) const { switch(condition) { ... case api_error::name_not_found: #if defined(_WIN32) return code == std::error_code(WSAEAI_NONAME, system_category()); #else return code == std::error_code(EAI_NONAME, getaddrinfo_category()); #endif ... default: return false; } }
getaddrinfo_category()
also needs to be defined somewhere.)
error_condition
:
bool api_category_impl::equivalent(const std::error_code & code, int condition) const { switch(condition) { case api_error::low_system_resources: return code == std::errc::not_enough_memory || code == std::errc::resource_unavailable_try_again || code == std::errc::too_many_files_open || code == std::errc::too_many_files_open_in_system; ... case api_error::no_such_entry: return code == std::errc::no_such_file_or_directory; default: return false; } }
const std::error_category & api_category();
api_category_impl api_category_instance; const std::error_category & api_category() { return api_category_instance; }
const std::error_category & api_category() { static api_category_impl instance; return instance; }
<system_error>
requires a function with a name make_error_code()
in order to associate an error value with a category:
std::error_condition make_error_condition(api_error e) { return std::error_condition ( static_cast<int>(e), api_category() ); }
error_code
. I will leave this as an exercise for the reader.
api_error
be used as constants error_condition
, enable the transform constructor using a template is_error_condition_enum
:
namespace std { template<> struct is_error_condition_enum<api_error>: public true_type {}; }
api_error
can be used as constants error_condition
, as well as those defined in std::errc
:
std::error_code ec; load_resource("http://some/url", ec); if(api_error::low_system_resources == ec) ...
<system_error>
.
Source: https://habr.com/ru/post/336012/