[ part 1 ]
error_code vs error_condition
Of the 1000+ pages of C++0x draft, the casual reader is bound to notice one thing: error_code and error_condition look virtually identical! What's going on? Is it a copy/paste error?
It's what you do with it that counts
Let's review the descriptions I gave in part 1:
- class error_code - represents a specific error value returned by an operation (such as a system call).
- class error_condition - something that you want to test for and, potentially, react to in your code.
The classes are distinct types because they're intended for different uses. As an example, consider a hypothetical function called create_directory():
void create_directory(
const std::string& pathname,
std::error_code& ec);
which you call like this:
std::error_code ec;
create_directory("/some/path", ec);
The operation can fail for a variety of reasons, such as:
- Insufficient permission.
- The directory already exists.
- The path is too long.
- The parent path doesn't exist.
Whatever the reason for failure, after create_directory() returns, the error_code object ec will contain the OS-specific error code. On the other hand, if the call was successful then ec contains a zero value. This follows the tradition (used by errno and GetLastError()) of having 0 indicate success and non-zero indicate failure.
If you're only interested in whether the operation succeeded or failed, you can use the fact that error_code is convertible-to-bool:
std::error_code ec;
create_directory("/some/path", ec);
if (!ec)
{
// Success.
}
else
{
// Failure.
}
However, let's say you're interested in checking for the "directory already exists" error. If that's the error then our hypothetical caller can continue running. Let's have a crack at it:
std::error_code ec;
create_directory("/some/path", ec);
if (ec.value() == EEXIST) // No!
...
This code is wrong. You might get away with it on POSIX platforms, but don't forget that ec will contain the OS-specific error. On Windows, the error is likely to be ERROR_ALREADY_EXISTS. (Worse, the code doesn't check the error code's category, but we'll cover that later.)
Rule of thumb: If you're calling error_code::value() then you're doing it wrong.
So here you have an OS-specific error code (EEXIST or ERROR_ALREADY_EXISTS) that you want to check against an error condition ("directory already exists"). Yep, that's right, you need an error_condition.
Comparing error_codes and error_conditions
Here is what happens when you compare error_code and error_condition objects (i.e. when you use operator== or operator!=):
- error_code against error_code - checks for exact match.
- error_condition against error_condition - checks for exact match.
- error_code against error_condition - checks for equivalence.
I hope that it's now obvious that you should be comparing your OS-specific error code ec against an error_condition object that represents "directory already exists". C++0x provides one for exactly that: std::errc::file_exists. This means you should write:
std::error_code ec;
create_directory("/some/path", ec);
if (ec == std::errc::file_exists)
...
This works because the library implementor has defined the equivalence between the error codes EEXIST or ERROR_ALREADY_EXISTS and the error condition std::errc::file_exists. In a future instalment I'll show how you can add your own error codes and conditions with the appropriate equivalence definitions.
(Note that, to be precise, std::errc::file_exists is an enumerator of enum class errc. For now you should think of the std::errc::* enumerators as placeholders for error_condition constants. In a later part I'll explain how that works.)
How to know what conditions you can test for
Some of the new library functions in C++0x have "Error conditions" clauses. These clauses list the error_condition constants and the conditions under which equivalent error codes will be generated.
A bit of history
The original error_code class was proposed for TR2 as a helper component for the filesystem and networking libraries. In that design, an error_code constant is implemented so that it matches the OS-specific error, where possible. When a match is not possible, or where there are multiple matches, the library implementation translates from the OS-specific error to the standard error_code, after performing the underlying operation.
In email-based design discussions I learnt the value of preserving the original error code. Subsequently, a generic_error class was prototyped but did not satisfy. A satisfactory solution was found in renaming generic_error to error_condition. In my experience, naming is one of the Hardest Problems in Computer Science, and a good name will get you most of the way there.
Next up, a look at the mechanism that makes enum class errc work as error_condition placeholders.
2 comments:
Greate post!
I've once use Asio to wrap MySQL C API and porting MySQL error codes to Boost.System. The tricky part is that, to get an error message, you must pass both the error code *and* the MySQL handle, while you can only specify a simple [error code -> error message] map without any other context information (like the MySQL handle). At last, I had to deprecate the message() method of the corresponding error_category subclass.
I wonder how can this issue be solved?
Very very very (very) useful explanation! Thank you Chris!
I am a little confused, why error_category uses `const char*` for `name()` and `std::string` for `message()`... It is bad for embedded.
Post a Comment