在POSIX系统中,使用errno构建std :: error_code,在Windows系统中使用GetLastError()。

33
我的问题是:如何正确构造来自POSIX的errno值和Windows上的GetLastError()std::error_code实例,以便可以将这些实例与std::errc中已知的值进行比较?
更详细的解释:我的目标是在C++11ish方式下为在POSIX和Windows系统上工作的自制异常对象添加一个std::error_code实例。
在我的跨平台应用程序中,我使用了一个自制的I/O类层次结构,它使用POSIX的fopen()和Windows的CreateFile()调用来打开/创建文件。如果失败,则会抛出一个通用的、自制的open_error异常(它派生自std::exception,但它不是C++预定义的异常类之一)。我正在尝试通过错误代码扩展这个相当简陋的异常;更准确地说,是使用C++11的std::error_code
我的问题是如何从errno(在POSIX情况下)或者GetLastError()(在Windows情况下)构造这样一个对象。对于POSIX,据我所知,我可以简单地在std::error_code的构造函数中使用errno,例如像这样:
std::error_code ec(errno, std::generic_category());

那个 ec 应该可以与 std::errc 的众所周知的值相媲美。

当然,对于 Windows 也可以进行类似的调用:

std::error_code ec(::GetLastError(), std::generic_category());

但我不确定GetLastError()返回的值是否与std::errc的常量相对应。我已经阅读了Boost系统库中的说明,对于Boost实现的error_code,它们是相对应的,但我想问的是std的实现,而不是Boost的。

请不要建议我切换到使用C++流进行文件访问。我很乐意这样做,但重构我的一半代码并不是我想在此时此刻做的事情。

3个回答

12
那是实现质量问题。要执行从平台本地错误代码枚举到标准的 std::error_condition 枚举的映射,需要依赖于由 std::system_category() 返回的 const 静态对象。在 17.6.5.14 错误代码值 [value.error.codes] 中:

鼓励基于 POSIX 的操作系统的实现定义与操作系统的值相同的值。

您可以在http://www.boost.org/doc/libs/1_46_1/libs/system/src/error_code.cpp中看到Boost如何执行映射;任何供Windows使用的编译器供应商提供的标准库都应该执行类似的操作。
预期的行为在19.5.1.5p4中进行了说明,描述了system_category().default_error_condition(int ev)

如果参数 ev 对应于 POSIX errno 值 posv,则函数应返回 error_condition(posv, generic_category())。 否则,函数应返回 error_condition(ev, system_category())

因此,例如,error_code(ERROR_FILE_NOT_FOUND, std::system_category()).default_error_condition()将调用std::system_category().default_error_condition(ERROR_FILE_NOT_FOUND),应返回std::error_condition(std::no_such_file_or_directory, std::generic_category())

因此,我的平台感知的I/O类可以使用error_code(::GetLastError(), std::generic_category())构建,平台无关的客户端代码可以通过例如if (std::errc::no_such_file_or_directory == caught_exception.code())进行检查,并且它应该可以正常工作,即使在Windows上,假设供应商实际上遵循17.6.5.14的鼓励? - Moritz Bunkus
4
你应该使用error_code(::GetLastError(), std::system_category()),而不是generic_category() - ecatmur
我实际上已经在Linux上使用g++ 4.7.2尝试过这个,使用了std::system_category()errno,但是在那种情况下比较失败了。而使用std::generic_category()则完全正常。 - Moritz Bunkus
1
啊,我看到你的编辑了。所以在 POSIX 上,我应该使用 error_code(errno, std::generic_category()) 构造,而在 Windows 上,我应该使用 error_code(::GetLastError(), std::system_category()) 是吗? - Moritz Bunkus
是的,这似乎是gcc(或者说是libstdc++)中的一个bug;根据1.9.5.1p4,它应该将POSIX错误代码映射到“generic_category”。你提出的解决方法现在听起来不错。 - ecatmur
谢谢您的解释,这确实消除了我对此的大部分困惑。 - Moritz Bunkus

6

这是一个老问题,但我在SO上没有找到一个好的答案。被接受的答案让我有些困惑,因为它似乎给出了一个error_condition而不是一个error_code。在POSIX上,我已经为自己选择了以下内容:

std::error_code error_code_from_errno(int errno_code) {
  return std::make_error_code(static_cast<std::errc>(errno_code));
}

这总是为我提供正确的类别(通用或系统)。过去我遇到过问题,因为同一errno代码的错误代码与另一个具有system_category的错误代码进行比较时不相等,因为一个具有generic_category


1
在使用 GetLastError() 获取值时,此代码无法在 Windows 上运行。make_error_code 使用 std::generic_category(),而 GetLastError() 需要 std::system_category() - MHebes
@MHebes make_error_code 使用实现了适当重载的 make_error_code。关键在于 static_cast 需要是正确的类型。 - sehe

1

看起来您应该使用system_category()来处理GetLastError()/errno,这样可以在两个平台上都正确处理。

如果您已经有了一个errc,则应该使用generic_category()(或make_error_code)。

以下是一个“地址已被使用”的错误测试。

#include <iostream>
#include <system_error>

#ifdef _WIN32
#include <WinError.h>
#define LAST_ERROR WSAEADDRINUSE
#else
#include <errno.h>
#define LAST_ERROR EADDRINUSE
#endif

#define ERRC std::errc::address_in_use

#define TRY(...)                                                              \
  {                                                                           \
    std::error_code ec = {__VA_ARGS__};                                       \
    std::cout << std::boolalpha << (ec == ERRC) << "\t" << ec.value() << "\t" \
              << ec.message() << "\n";                                        \
  }

int main() {
  TRY(static_cast<int>(ERRC), std::system_category())
  TRY(static_cast<int>(ERRC), std::generic_category()) // note: same as make_error_code
  TRY(static_cast<int>(LAST_ERROR), std::system_category())
  TRY(static_cast<int>(LAST_ERROR), std::generic_category()) // note: same as make_error_code
  return 0;
}

在Windows操作系统中:
false   100 Cannot create another system semaphore.
true    100 address in use
true    10048   Only one usage of each socket address (protocol/network address/port) is normally permitted.
false   10048   unknown error

On POSIX:

true    98  Address already in use
true    98  Address already in use
true    98  Address already in use
true    98  Address already in use

我使用这些等效错误代码的三元组进行测试时获得了类似的结果:

equivalent errc               Windows                 POSIX

errc::broken_pipe             ERROR_BROKEN_PIPE       EPIPE
errc::filename_too_long       ERROR_BUFFER_OVERFLOW   ENAMETOOLONG
errc::not_supported           ERROR_NOT_SUPPORTED     ENOTSUP
errc::operation_would_block   WSAEWOULDBLOCK          EWOULDBLOCK

如果有人感兴趣,这里是一份将std :: errc映射到== WinError.h常量的列表。 这是通过检查if (std::error_code(static_cast<int>(win_error_constant), std::system_category()) == errc)来完成的。

address_family_not_supported:
        WSAEAFNOSUPPORT
address_in_use:
        WSAEADDRINUSE
address_not_available:
        WSAEADDRNOTAVAIL
already_connected:
        WSAEISCONN
argument_list_too_long:
argument_out_of_domain:
bad_address:
        WSAEFAULT
bad_file_descriptor:
        WSAEBADF
bad_message:
broken_pipe:
        ERROR_BROKEN_PIPE
connection_aborted:
        WSAECONNABORTED
connection_already_in_progress:
        WSAEALREADY
connection_refused:
        WSAECONNREFUSED
connection_reset:
        WSAECONNRESET
cross_device_link:
        ERROR_NOT_SAME_DEVICE
destination_address_required:
        WSAEDESTADDRREQ
device_or_resource_busy:
        ERROR_BUSY_DRIVE
        ERROR_BUSY
        ERROR_OPEN_FILES
        ERROR_DEVICE_IN_USE
directory_not_empty:
        ERROR_DIR_NOT_EMPTY
executable_format_error:
file_exists:
        ERROR_FILE_EXISTS
        ERROR_ALREADY_EXISTS
file_too_large:
filename_too_long:
        ERROR_BUFFER_OVERFLOW
        WSAENAMETOOLONG
function_not_supported:
        ERROR_INVALID_FUNCTION
host_unreachable:
        WSAEHOSTUNREACH
identifier_removed:
illegal_byte_sequence:
inappropriate_io_control_operation:
interrupted:
        WSAEINTR
invalid_argument:
        ERROR_INVALID_HANDLE
        ERROR_INVALID_PARAMETER
        ERROR_NEGATIVE_SEEK
        ERROR_DIRECTORY
        ERROR_REPARSE_TAG_INVALID
        WSAEINVAL
invalid_seek:
io_error:
        ERROR_SEEK
        ERROR_WRITE_FAULT
        ERROR_READ_FAULT
        ERROR_OPEN_FAILED
        ERROR_CANTOPEN
        ERROR_CANTREAD
        ERROR_CANTWRITE
is_a_directory:
message_size:
        WSAEMSGSIZE
network_down:
        WSAENETDOWN
network_reset:
        WSAENETRESET
network_unreachable:
        WSAENETUNREACH
no_buffer_space:
        WSAENOBUFS
no_child_process:
no_link:
no_lock_available:
        ERROR_LOCK_VIOLATION
        ERROR_LOCKED
no_message_available:
no_message:
no_protocol_option:
        WSAENOPROTOOPT
no_space_on_device:
        ERROR_HANDLE_DISK_FULL
        ERROR_DISK_FULL
no_stream_resources:
no_such_device_or_address:
no_such_device:
        ERROR_INVALID_DRIVE
        ERROR_BAD_UNIT
        ERROR_DEV_NOT_EXIST
no_such_file_or_directory:
        ERROR_FILE_NOT_FOUND
        ERROR_PATH_NOT_FOUND
        ERROR_BAD_NETPATH
        ERROR_INVALID_NAME
no_such_process:
not_a_directory:
not_a_socket:
        WSAENOTSOCK
not_a_stream:
not_connected:
        WSAENOTCONN
not_enough_memory:
        ERROR_NOT_ENOUGH_MEMORY
        ERROR_OUTOFMEMORY
not_supported:
        ERROR_NOT_SUPPORTED
operation_canceled:
        ERROR_OPERATION_ABORTED
operation_in_progress:
        WSAEINPROGRESS
operation_not_permitted:
operation_not_supported:
        WSAEOPNOTSUPP
operation_would_block:
        WSAEWOULDBLOCK
owner_dead:
permission_denied:
        ERROR_ACCESS_DENIED
        ERROR_INVALID_ACCESS
        ERROR_CURRENT_DIRECTORY
        ERROR_WRITE_PROTECT
        ERROR_SHARING_VIOLATION
        ERROR_CANNOT_MAKE
        ERROR_NOACCESS
        WSAEACCES
protocol_error:
protocol_not_supported:
        WSAEPROTONOSUPPORT
read_only_file_system:
resource_deadlock_would_occur:
resource_unavailable_try_again:
        ERROR_NOT_READY
        ERROR_RETRY
result_out_of_range:
state_not_recoverable:
stream_timeout:
text_file_busy:
timed_out:
        WSAETIMEDOUT
too_many_files_open_in_system:
too_many_files_open:
        ERROR_TOO_MANY_OPEN_FILES
        WSAEMFILE
too_many_links:
too_many_symbolic_link_levels:
value_too_large:
wrong_protocol_type:
        WSAEPROTOTYPE.0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接