为什么std::make_error_code(std::errc)存在?



我理解std::error_codestd::error_condition的区别,并且我理解std::errc是一个"错误条件";枚举而不是"错误代码"。枚举。我了解如何将结果std::error_code值与"错误条件"进行比较;等等......多年来,我已经多次阅读了Chris Kohlhoff关于这些类型的博客文章。我已经在生产代码中使用了这些类型并对它们进行了扩展。

然而,我不明白为什么std::make_error_code(std::errc)存在。

对于比较实际的std::error_code值和std::error_condition值似乎没有必要:有std::make_error_condition(std::errc)和到std::error_condition的转换,以及所有这些的特殊比较重载。

这个函数的存在尤其令人费解,因为std::is_error_code_enum_v<std::err>falsestd::errc。据推测,这是为了防止通过std::make_error_code(std::errc)隐式地将std::errc转换为std::error_code,但我不清楚为什么std::make_error_code(std::errc)存在时防止这种情况是可取的。换句话说,如果你不应该把std::errc变成std::error_code,为什么有一个函数可以做到这一点呢?如果应该,为什么要禁用隐式构造函数?

是否std::errc是一个特例,因为代码通常希望在std::generic_category()中实际产生一个具有std::errc值的真正的std::error_code?换句话说,std::errc在某些方面是否同时具有和"错误码"?以及一个"错误条件";枚举?(如果是这样,为什么std::is_error_code_enum_v<std::errc>不是true呢?)

这是一个特殊的情况,因为一些其他的原因?

用户定义的错误条件枚举是否也应该像std::errc一样提供make_error_code(),或者只是make_error_condition()?

std::errc具有以下能力:

  1. 它可以用来创建error_condition,因为它已经定义了make_error_condition()
  2. 可以用来创建error_code,因为它定义了make_error_code()
  3. 它可以参与需要error_condition的操作,因为它是is_error_condition_enum

如果std::errc可以隐式转换为error_code,那么它将使用现有的显式转换(make_error_code)来实现可以想象的隐式转换。另一方面,make_error_code本身是有帮助的。

可以在出错时报告error_code的函数中使用make_error_code:

#include <algorithm>
#include <iostream>
#include <system_error>
#include <utility>
#include <vector>
struct minOrErrorResult {
int result;
std::error_code ec;
};
minOrErrorResult minOrError(const std::vector<int>& vec) noexcept {
if (vec.empty())
return { {}, make_error_code(std::errc::invalid_argument) };
int result = *std::min_element(vec.begin(), vec.end());
return { result, {} };
}
int main() {
const std::vector<int> examples[] = {
{ 3, 1, 2 },
{},
};
for (auto&& vec : examples) {
auto [result, ec] = minOrError(vec);
if (ec)
std::cout << "error: " << ec.message() << " [" << ec << "]n";
else
std::cout << "result: " << result << "n";
}
return 0;
}

在发生错误时抛出system_error异常的函数的类似示例:

int minOrException(const std::vector<int>& vec) {
if (vec.empty())
throw std::system_error(make_error_code(std::errc::invalid_argument));
int result = *std::min_element(vec.begin(), vec.end());
return result;
}
int main() {
const std::vector<int> examples[] = {
{ 3, 1, 2 },
{},
};
for (auto&& vec : examples) {
try {
auto result = minOrException(vec);
std::cout << "result: " << result << "n";
} catch (std::system_error& ex) {
std::cout << "error: " << ex.what() << " [" << ex.code() << "]n";
}
}
return 0;
}

如果没有使用make_error_code,我们可以假设std::errc常量没有被解释为error_code:

// Normally assume that rhs is an `error_condition`, never an `error_code`.
//                       ↓
if (lhs == std::errc::invalid_argument) {
// …
}

std::errc定义可移植错误的值;一组简单的(实际上,相当有限的)错误码值。

std::error_code是一个平台相关的错误,它包含一个错误代码值和一个对错误类别对象的对象的引用。


从程序设计的角度来看,从std::errcstd::error_code的隐式转换没有很好的理由。它们在逻辑上是不同的错误:普通vs.分组分类(来源)标准错误集与环境(平台和标准库实现)相关的错误集。

代码之间的转换不只是改变类型或数字表示,它通常是依赖于环境的逻辑。

为这种转换使用非成员函数是一个很自然的解决方案。


从实现方面来看,它们不是基本类型,所以编译器会特别支持与类型之间的转换;std::errc被实现为class enum枚举,不能像类那样实现转换操作符函数;std::error_code是一个类,所以可以实现std::errc的非显式构造函数,它支持隐式转换,就像std::io_errcstd::future_errc(*)一样,但是不同错误集的逻辑对应不是std::error_code所关心的,它会违反面向对象编程的原则(具体来说,它会不适当地在代码实体上分配责任)。


(*) -std::io_errcstd::future_errc(以及其他error code enumerations拥有)被隐式转换为std::error_code,因为它们与std::error_code存储相同的错误,std::error_code存储完全相同的错误码值和对错误对象相应类别的引用;错误集只是std::error code存储支持的全部集合的子集。


隐式转换只会使程序设计更糟糕,更容易出错(具体来说,在代码上的责任分配不合理,容易发生隐蔽的和不希望的转换)。

最新更新