std::system_error
in runet, I decided to translate several articles from the blog Andrzej Krzemieński, which I was advised in the comments to the previous post.std::error_code
. In this post I want to share my experience. enum class FlightsErrc { // 0 NonexistentLocations = 10, // DatesInThePast, // InvertedDates, // NoFlightsFound = 20, // ProtocolViolation = 30, // , XML ConnectionError, // ResourceError, // Timeout, // }; enum class SeatsErrc { // 0 InvalidRequest = 1, // , XML CouldNotConnect, // InternalError, // NoResponse, // NonexistentClass, // NoSeatAvailable, // };
int
should be enough.std::error_code
designed to store exactly this type of information: a number representing the status, and a “domain”, within which this number is assigned a value. In other words, std::error_code
is a pair: {int, domain}
. This is reflected in its interface: void inspect(std::error_code ec) { ec.value(); // ec.category(); // }
std::error_code
in this way. As we already said, two things we want to do is to log the state of std::error_code
how it was created (without subsequent conversion by higher levels of the application) and use it to answer a specific question, for example: “This error was caused by the user, provided incorrect data? ".std::error_code
instead of exceptions, let me clarify: these two things are not mutually exclusive. I want to report bugs in my program through exceptions. Inside the exception, instead of strings, I want to store std::error_code
, which I can easily check.std::error_code
has nothing to do with getting rid of exceptions. In addition, in my use case, I do not see a good reason to have many different types of exceptions. I will catch them all in one (or two) places, and I’ll learn about different situations by checking the object std::error_code
.std::error_code
so that it can store errors from the flight service described above: enum class FlightsErrc { // 0 NonexistentLocations = 10, // DatesInThePast, // InvertedDates, // NoFlightsFound = 20, // ProtocolViolation = 30, // , XML ConnectionError, // ResourceError, // Timeout, // };
enum
to std::error_code
: std::error_code ec = FlightsErrc::NonexistentLocations;
std::error_code
: void inspect(std::error_code ec) { if(ec) // : 0 != ec.value() handle_failure(ec); else handle_success(); }
FlightErrc
from 0. This, in turn, means that we can create an enumeration that does not correspond to any of the listed values: FlightsErrc fe {};
switch-statement
that “not all control paths return a value,” even if you have a case
label for each enumerated value.std::error_code
has a conversion constructor pattern that looks more or less like this: template<class Errc> requires is_error_code<Errc>::value error_code(Errc e) noexcept: error_code{make_error_code(e)} {}
std::is_error_code<Errc>::value
is evaluated as true
.)FlightErrc
, we need to make sure that:std::is_error_code<Errc>::value
returns true
.make_error_code
accepting the FlightsErrc
type FlightsErrc
defined and accessible via the ADL . namespace std { template<> struct is_error_code_enum<FlightsErrc>: true_type {}; }
std
is "legal."make_error_code
in the same namespace as FlightsErrc
: enum class FlightsErrc; std::error_code make_error_code(FlightsErrc);
make_error_code
, and we can put it in a separate translation unit (.cpp file).FlightsErrc
is an error_code
: std::error_code ec = FlightsErrc::NoFlightsFound; assert(ec == FlightsErrc::NoFlightsFound); assert(ec != FlightsErrc::InvertedDates);
error_code
is a pair: {number, domain}
, where the first element uniquely identifies the specific error situation in the domain, and the second uniquely identifies the error domain among all possible error domains that will ever be conceived. But, given that this domain identifier should be stored in one machine word, how can we guarantee that it will be unique to all libraries currently on the market and those that are still ahead? We hide the domain ID as an implementation detail. If we want to use another third-party library with our own enumeration of errors, how can we guarantee that their domain ID will not be equal to ours?std::error_code
is based on the observation that for each global object (or more formally: a namespace object) a unique address is assigned. Regardless of how many libraries and how many global objects are combined together, each global object has a unique address - this is quite obvious.error_code
, a unique global object, and then use its address as an identifier. Now the addresses will be used to represent the domain, this is exactly what std::error_code
does. But now, when we store some *
, the question arises: what should be
? A rather rational choice: let's use the type that can offer us additional benefits. So, the type T
is std::error_category
, and an additional advantage is in its interface: class error_category { public: virtual const char * name() const noexcept = 0; virtual string message(int ev) const = 0; // ... };
std::error_category
: for each new enumeration of errors, we need to get a new class inherited from std::error_category
. Typically, having pure virtual methods means selecting objects on the heap, but we will not do such things. We will create global objects and point to them.std::error_category
, which in other cases need to be configured, but we will not have to do this to connect FlightErrc
.std::error_category
, we need to override two methods. The name
method returns the short mnemonic name of the category (domain) of errors. The message
method assigns a text description for each numeric error value in this domain. To better illustrate this, let's define the error category for our FlightsErrc
listing. Remember that this class should only be visible in one translation unit. In other files we will simply use the address of its instance. namespace { struct FlightsErrCategory: std::error_category { const char * name() const noexcept override; std::string message(int ev) const override; }; const char * FlightsErrCategory::name() const noexcept { return "flights"; } std::string FlightsErrCategory::message(int ev) const { switch(static_cast<FlightsErrc>(ev)) { case FlightsErrc::NonexistentLocations: return "nonexistent airport name in request"; case FlightsErrc::DatesInThePast: return "request for a date from the past"; case FlightsErrc::InvertedDates: return "requested flight return date before departure date"; case FlightsErrc::NoFlightsFound: return "no filight combination found"; case FlightsErrc::ProtocolViolation: return "received malformed request"; case FlightsErrc::ConnectionError: return "could not connect to server"; case FlightsErrc::ResourceError: return "insufficient resources"; case FlightsErrc::Timeout: return "processing timed out"; default: return "(unrecognized error)"; } } const FlightsErrCategory theFlightsErrCategory {}; }
name
method provides a short text that is used when streaming std::error_code
to things like, for example, logs: this can help you determine the cause of the error. The text does not have to be unique in all enumeration errors: in the worst case, journal entries will be ambiguous.message
method provides a description text for any numeric value that represents an error in our category. This can be useful when debugging or viewing logs; but you probably don’t want to show this text to users without further processing.FlightErrc
, so we must explicitly bring it back to FlightErrc
. I believe that the example in the above article does not compile because of the static_cast
skipped. After casting, there is a risk that we will check a value that does not apply to the enumeration: therefore, we need a default
label.FlightErrCategory
. This will be the only object of this type in the program. We will need his address, but we will also use his polymorphic properties.std::error_category
not a literal type, it has a constexpr
default constructor. The implicitly declared default constructor of our FlightErrCategory
class inherits this property. Thus, we ensure that the initialization of our global object is performed during the initialization of constants, as described in this article, and therefore does not contain any problems with the order of static initialization .make_error_code
: std::error_code make_error_code(FlightsErrc e) { return {static_cast<int>(e), theFlightsErrCategory}; }
FlightsErrc
can be used as if it were std::error_code
: int main() { std::error_code ec = FlightsErrc::NoFlightsFound; std::cout << ec << std::endl; }
flights: 20
std::error_code
objects, but this will be a topic for another article.std::error_code
. In addition to the series of articles from Christopher Kohlhoff, I was also able to learn about the std::error_code
from the documentation for the Outcome library from Niall Douglas, here .Source: https://habr.com/ru/post/340604/
All Articles