我理解
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::errc
隐式地将std::error_code
转换为std::make_error_code(std::errc)
,但我不清楚为什么在存在std::make_error_code(std::errc)
时阻止这种情况是可取的。换句话说,如果你不应该用 std::error_code
来制作 std::errc
,为什么有一个函数可以做到这一点呢?如果你应该这样做,为什么隐式构造函数被禁用?
这是
std::errc
的特殊情况吗,因为代码通常希望在 std::error_code
中实际生成具有 std::generic_category()
值的真实 std::errc
吗?换句话说,std::errc
在某些方面both是“错误代码”和“错误条件”枚举吗? (如果是这样,为什么不是 std::is_error_code_enum_v<std::errc>
true
?)
是否有其他原因造成的特殊情况?
用户定义的错误条件枚举是否也应该像
make_error_code()
那样提供 std::errc
,或者只是 make_error_condition()
?
std::errc
定义了可移植误差的值;一组简单的(事实上,非常有限的)错误代码值。
std::error_code
是与平台相关的错误,其中包含错误代码值和对错误类别对象的对象的引用。
从程序设计的角度来看,从
std::errc
到std::error_code
的隐式转换没有充分的理由。它们在逻辑上是非常不同的错误集:
简单与分组(来源)
标准错误集与环境(平台和标准库实现)相关的错误集。
代码之间的转换不仅仅是改变类型或数字表示,它通常是依赖于环境的逻辑。
用于此类转换的非成员函数是一个非常自然的解决方案。
从实施方面来看, 它们不是基本类型,因此编译器将专门支持类型之间的转换;
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
。
隐式转换只会使程序设计变得更糟并且容易出错(具体来说,代码上的责任分配不合逻辑,并且容易发生隐蔽和不需要的转换)。
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
本身就有帮助。
我们可以在出现错误时报告
make_error_code
的函数中使用 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) {
// …
}