36

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

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

但是,errnoGetLastError()不能使用同一个类,否则有些错误代码会产生歧义。错误代码 33 就是一个示例,因为它同时是EDOMERROR_LOCK_VIOLATION

甚至有些地方提倡 WinAPI 的用户自定义类别,但我目前找不到任何参考。这种选择会特别痛苦。

应该使用哪个类别errno,以及应该使用哪个类别,GetLastError()以便

  • std::error_code::default_error_condition()
  • std::error_code::message()

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

4

3 回答 3

21

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

如果在 POSIX 上:

generic_category=> POSIX 标准错误空间

system_category=> 本地 POSIX errno 空间(通常使用专有的 errno 代码扩展 POSIX)。用于strerror()将代码扩展为由 . 返回的字符串描述message()

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

如果在 Windows 上:

generic_categoryfopen()=> POSIX 标准 errno 空间,由 MSVCRT等中的各种 POSIX 仿真函数返回

system_category=> Win32GetLastError()空间。用于FormatMessage()将代码扩展为由 . 返回的字符串描述message()

如何可移植地使用 <system_error>

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

其他想法:

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

确实,如果 constexpr 全局变量在其设计时可用,今天看起来会完全不同,也许这可能会在 17 之后的 C++ 标准中得到纠正。但是如果你是一个需要从错误代码中移动的程序员第三方库不会通过未编写的代码来了解这些第三方库而丢失信息,那么 <system_error> 是一个很好的解决方案。

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

于 2016-10-15T18:57:36.187 回答
18

在 C++ 标准中:

system_category

当前的C++17 草案指出:

C++ 标准库中的某些函数通过std::error_code(19.5.2.1) 对象报告错误。该对象的category()成员应返回std::system_category() 源自操作系统的错误, 或返回对源自其他地方的错误的实现定义error_category 对象的引用。实现应为这些错误 > 类别中的每一个定义 value() 的可能值。[ 示例:对于基于 POSIX 的操作系统,鼓励实现将值定义std::system_category() 为与 POSIXerrno值相同,并使用操作系统文档定义的附加值。 鼓励不基于 POSIX 的操作系统的实现定义与操作系统值相同的值。对于并非源自操作系统的错误,实现可以为相关值提供枚举。

这不是很清楚:

  • errnoWindows上的值应该发生什么?

  • errno来自 POSIX 调用“源自操作系统”还是应该仅限于非 POSIX 调用?

generic_category

  • std::errcEFOOBAR是与 C/POSIX错误代码具有相同值的枚举;

    每个常数的值应与上述概要中所示enum errc的宏的值相同。<cerrno>未指定实现是否公开 <cerrno>宏。

  • make_error_code(std::errc)生成一个erro_code使用generic_category

    error_code make_error_code(errc e) noexcept;

    回报:error_code(static_cast<int>(e), generic_category())

这意味着 POSIX 错误代码可以与generic_category. 非 POSIX 值可能无法与generic_catgeory. 在实践中,它们似乎得到了我一直在使用的实现的支持。

在升压

升压系统本身

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

最初的提议将错误类别视为 errno(即 POSIX 样式)和本机操作系统的错误代码之间的二元选择。

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

static const error_category & errno_ecat = generic_category();

linux_error.hpp

在 API 错误后构造 error_code:error_code( errno, system_category() )

windows_error.hpp

在 API 错误后构造 error_code:error_code( ::GetLastError(), system_category() )

cygwin_error.hpp

在 API 错误后构造 error_code:error_code( errno, system_category() )

对于 Windows,Boostsystem_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;
}

我们errnosystem_categoryPOSIX 代码中发现:

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

文件系统

我们errnogeneric_categoryPOSIX 代码中找到:

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++ 中

文件系统

我们errno发现generic_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非 POSIXerrno与 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

库++

我们errno发现system_category

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

但没有使用generic_category.

结论

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

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

  • 您可以安全地使用generic_categoryPOSIX 值errno

  • 你不应该能够std::generic_category用于非POSIX vales errno(它可能不起作用);

  • 如果您不想检查您的errno值是否为 POSIX 值:在基于 POSIX 的系统上,您应该能够使用system_errorerrno严格来说,对此的支持不是强制性的,只是鼓励)。 在基于 POSIX 的系统上,您可以system_error使用errno.

新提案(2019-12 更新)

有人提议引入一个新的错误系统 ( std::error, std::status_code)。

有关设施问题的讨论,请参见相关讨论及其第 4 节<system_error>

  • 使用 std::string
  • “双 API”库的激增
  • 没有任何措辞搁置 0 枚举数
  • 依赖单身人士
  • 没有 error_category 子类可以是文字类型
  • 没有关于将额外信息附加到 error_code 的指导
  • 依赖于令人惊讶的 operator== 过载
  • error_category 应该正确命名为 error_domain
  • 标准的 error_code-yielding 函数无论如何都会抛出异常
  • 未指定的 error_code 比较语义
于 2016-02-23T13:03:56.183 回答
1

这里的混乱!

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

generic_category()并且system_category()用于errno 值。它们之间的区别是基于 errno 的值:

  • POSIX 指定的值与 generic_category() 一起使用以创建error_condition可移植的 s
  • 具有 POSIX 等效项的值被转换并与 generic_category() 一起使用
  • 操作系统实现提供的值,在 POSIX 值之外,与 system_category() 一起使用——创建error_code不可移植的 s

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

  • error_code对象用于“低级”和系统特定的错误
  • error_condition对象是可移植的错误,例如由标准定义
  • 它们使用不同error_category的对象,因为它们的数值被转换为不同的错误字符串。
  • 您可以尝试使用该方法将特定于系统的系统映射error_code到便携式设备,无论是在.error_conditiondefault_error_condition()error_codeerror_category

但是,当没有从error_code到 的转换error_condition(或没有实现)时,您会得到一个error_condition基于系统特定的error_category,这已经违背了可移植映射的目的。这就是 C++ 标准库的状态,我猜……!

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

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

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

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

于 2021-01-06T00:36:48.910 回答