问题
C++11 introduced the <system_error>
header containing a generic system to handle error codes. An std::error_code
is a tuple containing an int
, the error code, and a reference to an std::error_category
, which defines the error domain and handling of the error code. The standard library comes with four categories: std::generic_category
, std::system_category
, std::future_category
, and std::iostream_category
.
There are conflicts on which category to use, both here on SO and on C++ reference sites, when creating std::error_code
s/throwing std::system_error
s with errno
and WinAPI error codes:
errno
withstd::generic_category
: SO answer, llvm-commits, cplusplus.comerrno
withstd::system_category
: SO answer, cppreference.comGetLastError()
withstd::generic_category
: SO answerGetLastError()
withstd::system_category
: SO answer, SO comment
However, errno
and GetLastError()
can't use the same category, otherwise some error codes would be ambiguous. Error code 33 is one example, as it is both EDOM
and ERROR_LOCK_VIOLATION
.
There are even some places advocating a user-made category for the WinAPI, but I can't find any references to that at the moment. This alternative would be specially painful.
Which category should be used with errno
, and which should be used with GetLastError()
so that
std::error_code::default_error_condition()
std::error_code::message()
are unambinguous and appropriate to the underlying error code?
回答1:
In the C++ standard:
system_category
The current C++17 draft states that:
Certain functions in the C ++ standard library report errors via a
std::error_code
(19.5.2.1) object. That object’scategory()
member shall returnstd::system_category()
for errors originating from the operating system, or a reference to an implementation-definederror_category
object for errors originating elsewhere. The implementation shall define the possible values of value() for each of these error > categories. [ Example: For operating systems that are based on POSIX, implementations are encouraged to define thestd::system_category()
values as identical to the POSIXerrno
values, with additional values as defined by the operating system’s documentation. Implementations for operating systems that are not based on POSIX are encouraged to define values identical to the operating system’s values. For errors that do not originate from the operating system, the implementation may provide enums for the associated values.
It's not so clear:
what is supposed to happen to
errno
values on Windows?is an
errno
from a POSIX call "originating from the operating system" or is this supposed to be restricted to non POSIX calls?
generic_category
std::errc
is an enumeration with the same values as the C/POSIXEFOOBAR
errors code;The value of each
enum errc
constant shall be the same as the value of the<cerrno>
macro shown in the above synopsis. Whether or not the implementation exposes the<cerrno>
macros is unspecified.make_error_code(std::errc)
generates anerro_code
usinggeneric_category
error_code make_error_code(errc e) noexcept;
Returns:
error_code(static_cast<int>(e), generic_category())
.
This means that POSIX error code can be used with generic_category
. Non POSIX values might possibly not work correctly with generic_catgeory
. In practice, they seem to be supported by the implementations I've been using.
In Boost
Boost system itself
The Boost documentation is quite terse about this feature:
The original proposal viewed error categories as a binary choice between errno (i.e. POSIX-style) and the native operating system's error codes.
Moreover you can find legacy declaration such as:
static const error_category & errno_ecat = generic_category();
In linux_error.hpp
:
To construct an error_code after a API error:
error_code( errno, system_category() )
In windows_error.hpp
:
To construct an error_code after a API error:
error_code( ::GetLastError(), system_category() )
In cygwin_error.hpp
:
To construct an error_code after a API error: error_code( errno, system_category() )
For Windows, Boost uses system_category
for non errno
errors:
ec = error_code( ERROR_ACCESS_DENIED, system_category() );
ec = error_code( ERROR_ALREADY_EXISTS, system_category() );
ec = error_code( ERROR_BAD_UNIT, system_category() );
ec = error_code( ERROR_WRITE_PROTECT, system_category() );
ec = error_code( WSAEWOULDBLOCK, system_category() );
In ASIO
We find this kind of code in ASIO:
template <typename ReturnType>
inline ReturnType error_wrapper(ReturnType return_value,
boost::system::error_code& ec)
{
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
ec = boost::system::error_code(WSAGetLastError(),
boost::asio::error::get_system_category());
#else
ec = boost::system::error_code(errno,
boost::asio::error::get_system_category());
#endif
return return_value;
}
We find errno
as system_category
in POSIX code:
int error = ::pthread_cond_init(&cond_, 0);
boost::system::error_code ec(error,
boost::asio::error::get_system_category());
Filesystem
We find errno
with generic_category
in POSIX code:
if (::chmod(p.c_str(), mode_cast(prms)))
{
if (ec == 0)
BOOST_FILESYSTEM_THROW(filesystem_error(
"boost::filesystem::permissions", p,
error_code(errno, system::generic_category())));
else
ec->assign(errno, system::generic_category());
}
In GNU libstdc++
Filesystem
We find errno
with generic_category
:
if (char* rp = ::realpath(pa.c_str(), buf.get())) {
[...]
}
if (errno != ENAMETOOLONG) {
ec.assign(errno, std::generic_category());
return result;
}
and no usage of system_category
.
Using libstdc++
In practice, it seems you can use generic_category
for non-POSIX errno
with libstdc++:
std::error_code a(EADV, std::generic_category());
std::error_code b(EADV, std::system_category());
std::cerr << a.message() << '\n';
std::cerr << b.message() << '\n';
Gives:
Advertise error
Advertise error
Libc++
We find errno
with system_category
:
int ec = pthread_join(__t_, 0);
if (ec)
throw system_error(error_code(ec, system_category()), "thread::join failed");
but no usage of generic_category
.
Conclusion
I don't find any consistent pattern here but apparently:
you are expected to use
system_category
when using Windows error on Windows;you can safely use
generic_category
for POSIX values oferrno
;you are not supposed to be able to use
std::generic_category
for non-POSIX vales oferrno
(it might not work);If you do not want to check if your
errno
value is a POSIX one:on POSIX-based systems you are expected to be able to useon POSIX-based systems you can usesystem_error
witherrno
(strictly speaking the support for this is not mandated, only encouraged).system_error
witherrno
.
回答2:
I have to admit to a bit of surprise at the confusion regarding <system_error> given Chris summarised exactly how it works at http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html and I personally find the C++ standard text above perfectly clear. But to summarise in very succinct words:
If on POSIX:
generic_category
=> POSIX standard errno space
system_category
=> Local POSIX errno space (usually extends POSIX with proprietary errno codes). Use strerror()
to expand codes into string descriptions returned by message()
.
In practice on POSIX both implementations are the same underneath and map the native errno space.
If on Windows:
generic_category
=> POSIX standard errno space which is returned by various POSIX emulation functions in the MSVCRT like fopen()
etc
system_category
=> The Win32 GetLastError()
space. Use FormatMessage()
to expand codes into string descriptions returned by message()
.
How to use <system_error> portably
std::error_code ec;
#ifdef _WIN32
if((HANDLE)-1 == CreateFile(...))
ec = std::error_code(GetLastError(), std::system_category());
#else
if(-1 == open(...))
ec = std::error_code(errno, std::system_category());
#endif
// To test using portable code
if(ec == std::errc::no_such_file_or_directory)
...
// To convert into nearest portable error condition (lossy, may fail)
std::error_condition ec2(ec.default_error_condition())
Other thoughts:
Some commentators have said that <system_error> is poorly designed and shouldn't be used. This is simply not true, it's pretty optimal given the C++ 03 idiomatic practice of the time of its design, it generates very tight high quality fixed latency code on all major STLs except Dinkumware's. It's user extensible to any arbitrary error code system, and standardises unifying into a single system disparate third party library error handling.
It is true it would look quite different today had constexpr global variables been available at the time of its design, and maybe that might get rectified in a C++ standard coming after 17. But if you are a programmer who needs to move around error codes from third party libraries without losing information through code not written to know about those third party libraries, then <system_error> is an excellent solution.
Consider it as similar to the virtual
keyword for third party library error code handling - it erases the need for code transporting third party codes from needing to understand those codes. If you have that problem in your code base - and most large code bases do - then absolutely you should be using <system_error> instead of whatever error code mapping or translation system you're currently using.
来源:https://stackoverflow.com/questions/28746372/system-error-categories-and-standard-system-error-codes