【问题标题】:<system_error> categories and standard/system error codes<system_error> 类别和标准/系统错误代码
【发布时间】:2015-04-29 01:28:25
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • 真正的 问题是 C++ 标准化委员会有时似乎忽略了这一点:dailyzooniverse.files.wordpress.com/2014/04/… 并通过仅考虑 posix 派生接口来定义某些东西。我怀疑该标准是否会适应这一点。
  • 老实说,不要使用&lt;system_error&gt;。它的实现很差,很难使用,我真的不知道为什么它被包含在标准中。
  • 创建GetLastError() 类别有多难?我能想到的唯一两个麻烦点是:a)定义名称(要么你必须为所有这些错误拥有一百万个字符串常量,要么你必须做一些通用的事情,比如“错误代码 nnn” ) 和 b) default_error_condition() 是否指定要为“无映射”返回的内容?因为 cppreference.com 没有说
  • message() 至少有礼貌地成为std::string 而不是静态缓冲区,因此您可以在运行时调用FormatMessage() 并完成它。然后你有一个有趣的 COM 错误问题,如果你担心在 COM 错误上 FormatMessage() 的记录性,至少在 C++ 中你可以使用 _com_error 及其 ErrorMessage() 方法,但名称仍然是一个问题...而且COM错误也可以封装GetLastError()代码...

标签: c++ c++11 winapi posix std-system-error


【解决方案1】:

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

如果在 POSIX 上:

generic_category => POSIX 标准错误空间

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

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

如果在 Windows 上:

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

system_category => Win32 GetLastError() 空间。使用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 关键字 - 它消除了传输第三方代码的代码的需要,无需理解这些代码。如果您的代码库中存在这个问题 - 大多数大型代码库都有 - 那么您绝对应该使用 而不是您当前使用的任何错误代码映射或翻译系统。

【讨论】:

【解决方案2】:

在 C++ 标准中:

system_category

current C++17 draft 声明:

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

不是很清楚:

  • Windows 上的errno 值会发生什么情况?

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

generic_category

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

    每个enum errc 常量的值应与 上述概要中显示的&lt;cerrno&gt; 宏的值。 实现是否暴露 &lt;cerrno&gt; 未指定宏。

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

    error_code make_error_code(errc e) noexcept;

    返回:error_code(static_cast&lt;int&gt;(e), generic_category())

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

在增强中

Boost 系统本身

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

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

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

static const error_category &amp; 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,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)。

有关&lt;system_error&gt; 设施问题的讨论,请参阅relevant discussion 及其第4 节:

  • std::string 的使用
  • “双 API”库的激增
  • 没有任何措辞将 0 枚举数放在一边
  • 对单例的依赖
  • error_category 子类不能是文字类型
  • 没有关于将额外信息附加到 error_code 的指导
  • 依赖于令人惊讶的 operator== 过载
  • error_category 应该正确命名为 error_domain
  • 标准的 error_code-yielding 函数无论如何都会抛出异常
  • 未指定的 error_code 比较语义

【讨论】:

  • 我们只能责怪微软定义了两个不同且不相关的错误编号系统。很久以前,他们真的在这件事上丢了球。由于errno 基于#defines,他们可以轻松地使值兼容,但没有。
  • @rustyx,我倾向于认为真正的问题在于 C++ 标准。拥有不同的类别是有意义的:很多库和 API 都有自己的错误编号(OpenGL、OpenCL、Mach ......)但我不太确定通用 system_category 的价值:我们可以只使用 generic_category errno 和系统特定 (windows_category) 类别用于系统特定错误。
  • 也是正确的,但显然system_category 是针对来自系统的错误,所以很自然地将它与errno 代码一起使用,因为POSIX 系统错误 errno 代码:...the integer variable errno, which is set by system calls and some library functions in the event of an error to indicate what went wrong.。要求 POSIX 开发人员为此使用 generic_category 是徒劳的,即使这会使代码不可移植。所以这个标准是有缺陷的(但主要是关于 Win32)。
  • @rustyx,嗯,我不清楚什么是“系统”(以及系统是否只返回一组给定的错误代码)。建立在 Mach 系统之上的 POSIX 系统呢?您有返回马赫错误代码的马赫调用和返回错误代码的 POSIX 调用。
  • 我没有看到上述的不一致,除了可能是 GNU libstdc++。在所有示例中,system_category 用于来自 Windows 上的GetLastError 或基于 Unix 的系统上的errno 的系统错误,generic_category 专门用于来自errno 的 POSIX 错误代码。符合 POSIX 标准的系统的系统错误代码来自 errno 并且在数字上等于 generic_category 中的错误这一事实只是一个巧合,因为 generic_category 和系统使用相同的标准化代码,因为这些系统也使用errno 返回错误。
【解决方案3】:

这里太混乱了!

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

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

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

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

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

但是,当没有从 error_codeerror_condition 的转换(或没有实现)时,您会得到一个基于系统特定 error_categoryerror_condition,这已经违背了便携式映射的目的.这就是C++标准库的状态,我猜……!

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

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

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

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

【讨论】:

  • 第二句话中的极端双关语! +1。
猜你喜欢
  • 1970-01-01
  • 2014-11-02
  • 2017-08-22
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 1970-01-01
  • 2017-10-23
相关资源
最近更新 更多