我理解std::error_code
和std::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>
是false
的std::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
具有以下能力:
- 它可以用来创建
error_condition
,因为它已经定义了make_error_condition()
。 - 可以用来创建
error_code
,因为它定义了make_error_code()
。 - 它可以参与需要
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::errc
到std::error_code
的隐式转换没有很好的理由。它们在逻辑上是不同的错误:普通vs.分组分类(来源)标准错误集与环境(平台和标准库实现)相关的错误集。
代码之间的转换不只是改变类型或数字表示,它通常是依赖于环境的逻辑。
为这种转换使用非成员函数是一个很自然的解决方案。
从实现方面来看,它们不是基本类型,所以编译器会特别支持与类型之间的转换;std::errc
被实现为class enum
枚举,不能像类那样实现转换操作符函数;std::error_code
是一个类,所以可以实现std::errc
的非显式构造函数,它支持隐式转换,就像std::io_errc
和std::future_errc
(*)一样,但是不同错误集的逻辑对应不是std::error_code
所关心的,它会违反面向对象编程的原则(具体来说,它会不适当地在代码实体上分配责任)。
(*) -std::io_errc
和std::future_errc
(以及其他error code enumerations
拥有)被隐式转换为std::error_code
,因为它们与std::error_code
存储相同的错误,std::error_code
存储完全相同的错误码值和对错误对象相应类别的引用;错误集只是std::error code
存储支持的全部集合的子集。
隐式转换只会使程序设计更糟糕,更容易出错(具体来说,在代码上的责任分配不合理,容易发生隐蔽的和不希望的转换)。