<system_error> 个类别和 standard/system 个错误代码

<system_error> categories and standard/system error codes

C++11 引入了 <system_error> header,其中包含用于处理错误代码的通用系统。 std::error_code 是一个包含 int、错误代码和对 std::error_category 的引用的元组,它定义了错误域和错误代码的处理。标准库有四个类别:std::generic_categorystd::system_categorystd::future_categorystd::iostream_category.

当使用 errno 和 WinAPI 创建 std::error_codes/throwing std::system_errors 时,在 SO 和 C++ 参考站点上使用哪个类别存在冲突错误代码:

但是,errnoGetLastError()不能使用同一个类别,否则某些错误代码会产生歧义。错误代码 33 就是一个示例,因为它既是 EDOM 又是 ERROR_LOCK_VIOLATION.

甚至有些地方提倡 WinAPI 的 user-made 类别,但目前我找不到任何相关的参考资料。这种选择会特别痛苦。

哪些类别应该与errno一起使用,哪些应该与GetLastError()一起使用,以便

是否明确且适用于底层错误代码?

在 C++ 标准中:

system_category

current C++17 draft 指出:

Certain functions in the C ++ standard library report errors via a std::error_code (19.5.2.1) object. That object’s category() member shall return std::system_category() for errors originating from the operating system, or a reference to an implementation-defined error_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 the std::system_category() values as identical to the POSIX errno 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.

不太清楚:

  • Windows 上的 errno 值应该发生什么?

  • 是来自 POSIX 呼叫的 errno "originating from the operating system" 还是应该仅限于非 POSIX 呼叫?

generic_category

  • std::errc 是一个枚举,其值与 C/POSIX EFOOBAR 错误代码相同;

    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) 使用 generic_category

    生成 erro_code

    error_code make_error_code(errc e) noexcept;

    Returns: error_code(static_cast<int>(e), generic_category()).

这意味着 POSIX 错误代码可以与 generic_category 一起使用。非 POSIX 值可能无法与 generic_catgeory 一起正常工作。实际上,它们似乎得到了我一直在使用的实现的支持。

提升中

提升系统本身

Boost 文档对此功能非常简洁:

The original proposal viewed error categories as a binary choice between errno (i.e. POSIX-style) and the native operating system's error codes.

此外,您还可以找到遗留声明,例如:

static const error_category & errno_ecat = generic_category();

linux_error.hpp中:

To construct an error_code after a API error: error_code( errno, system_category() )

windows_error.hpp中:

To construct an error_code after a API error: error_code( ::GetLastError(), system_category() )

cygwin_error.hpp中:

To construct an error_code after a API error: error_code( errno, system_category() )

对于 Windows,Boost 使用 system_category 来处理非 errno 错误:

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() );

在 ASIO 中

我们在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;
}

我们在POSIX代码中发现errnosystem_category

int error = ::pthread_cond_init(&cond_, 0);
boost::system::error_code ec(error,
    boost::asio::error::get_system_category());

文件系统

我们在POSIX代码中找到errnogeneric_category

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());

}

在 GNU libstdc++ 中

文件系统

我们找到 errnogeneric_category:

if (char* rp = ::realpath(pa.c_str(), buf.get())) {
  [...]
}
if (errno != ENAMETOOLONG) {
  ec.assign(errno, std::generic_category());
  return result;
}

并且没有使用 system_category

使用 libstdc++

实际上,您似乎可以将 generic_category 用于非 POSIX errno 与 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';

给出:

Advertise error
Advertise error

Libc++

我们发现 errnosystem_category:

int ec = pthread_join(__t_, 0);
if (ec)
  throw system_error(error_code(ec, system_category()), "thread::join failed");

但没有使用 generic_category

结论

我在这里没有找到任何一致的模式,但显然:

  • 在 Windows 上使用 Windows 错误时,您应该使用 system_category

  • 您可以安全地将 generic_category 用于 errno 的 POSIX 值;

  • 您不应该将 std::generic_category 用于 errno 的非 POSIX 值(它可能不起作用);

  • 如果你不想检查你的 errno 值是否是 POSIX 一个: 在基于 POSIX 的系统上你是预计能够将 system_errorerrno 一起使用(严格来说,对此的支持不是强制性的,只是鼓励)。 在基于 POSIX 的系统上,您可以使用 system_errorerrno.

新提案(更新 2019-12)

有人提议引入新的错误系统(std::errorstd::status_code)。

有关 <system_error> 设施问题的讨论,请参阅 relevant discussion 及其第 4 节:

  • use of std::string
  • proliferation of "two-API" libraries
  • no wording sets aside the 0 enumerator
  • reliance on singletons
  • no error_category subclass can be a literal type
  • no guidance on attaching extra information to error_code
  • reliance on a surprising overload of operator==
  • error_category should properly have been named error_domain
  • standard error_code-yielding functions can throw exceptions anyway
  • underspecified error_code comparison semantics

鉴于 Chris 在 http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html 中准确地总结了它的工作原理,我不得不承认对 的混淆感到有些惊讶,而且我个人认为上面的 C++ 标准文本非常清楚。但是用非常简洁的话来总结一下:

如果在 POSIX:

generic_category => POSIX 标准错误号 space

system_category => Local POSIX errno space(通常使用专有的 errno 代码扩展 POSIX)。使用 strerror() 将代码扩展为 message().

返回的字符串描述

在 POSIX 上的实践中,两种实现在下面是相同的,并且映射本机 errno space。

如果在 Windows:

generic_category => POSIX 标准错误号 space 由 MSVCRT 中的各种 POSIX 仿真函数返回,例如 fopen()

system_category => Win32 GetLastError() space。使用 FormatMessage() 将代码扩展为 message().

返回的字符串描述

如何便携使用

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())

其他想法:

一些评论员说 设计不佳,不应使用。这根本不是真的,考虑到其设计时的 C++ 03 惯用做法,它是非常理想的,它在除 Dinkumware 之外的所有主要 STL 上生成非常严格的高质量固定延迟代码。它是用户可扩展到任何任意错误代码系统,并将不同的第三方库错误处理统一到一个单一系统中的标准化。

确实,如果在设计时就提供了 constexpr 全局变量,那么今天它看起来会大不相同,也许在 17 之后的 C++ 标准中可能会得到纠正。但是如果您是一名程序员,需要移动来自第三方库的错误代码而不丢失信息 通过未编写的代码来了解这些第三方库,然后优秀解决方案。

将其视为类似于第三方库错误代码处理的 virtual 关键字 - 它消除了传输第三方代码的代码需要理解这些代码。如果你的代码库中有这个问题——大多数大型代码库都有——那么你绝对应该使用 而不是你当前使用的任何错误代码映射或翻译系统。

这里太混乱了!

错误类别是错误的来源。 iostreams 库会产生自己的错误,因此它有自己的类别。同样,WinAPI 是它自己的错误来源,因此需要一个用户定义的错误类别。

generic_category()system_category() 都是 both 错误号值。它们之间的区别是根据errno的值:

  • 由 POSIX 指定的值与 generic_category() 一起使用以创建 error_condition 可移植的
  • 具有 POSIX 等价物的值被翻译并与 generic_category()
  • 一起使用
  • 操作系统实现提供的 POSIX 值之外的值与 system_category() 一起使用——创建 error_codes,它们不可移植

<system_error> 库是围绕 error_codeerror_condition 之间的(隐含的)区别构建的。两个类有相同的方法和相同的接口,除了:

  • error_code 对象用于“低级别”和系统特定错误
  • error_condition 对象是可移植错误,例如由标准
  • 定义
  • 它们使用不同的 error_category 对象,因为它们的数值会被转换为不同的错误字符串。
  • 您可以尝试使用 default_error_condition() 方法在 error_code 或其 [=19= 上将特定于系统的 error_code 映射到便携式 error_condition ].

然而,如果没有从 error_codeerror_condition 的转换(或者 none 已实现),您会得到一个 error_condition 基于系统特定的 error_category,这已经违背了便携式映射的目的。这就是 C++ 标准库的状态,我猜……!

error_category 对象知道它们代表可移植值还是不可移植值。 system_category() 标识系统特定值(errno)并将它们转换为 POSIX 值(如果可用),然后使用 generic_category() 将这些值映射到 error_condition.

每个类别都知道将数字错误转换为字符串(描述)。 system_category() 将特定的 ęrrno 值转换为字符串,很可能使用标准 strerror()sys_errlist.

因此,将 ::GetLastError() 中的 WinAPI 值用于此类别是一个编程错误。

例如,使用 WinSock2 API,它提供来自 ::WSAGetLastError() 函数的错误值,需要另一个错误类别。