我最近开始探索在现代 C++(C++17 及更高版本)中使用
std::variant
来处理错误并同时从函数返回值,有点类似于 Rust 的 Result
类型。我了解 std::variant
的基本用法,但我正在寻找专为错误处理量身定制的高效且优雅的模式。
这是我迄今为止尝试过的:
#include <iostream>
#include <variant>
#include <string>
using Result = std::variant<std::string, int>;
Result computeValue(int x) {
if (x < 0) {
return "Negative input error";
}
return x * 2;
}
void handleResult(const Result& result) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::string>) {
std::cout << "Error: " << arg << std::endl;
} else {
std::cout << "Computed value: " << arg << std::endl;
}
}, result);
}
int main() {
Result res1 = computeValue(10);
Result res2 = computeValue(-1);
handleResult(res1);
handleResult(res2);
return 0;
}
我目前正在寻找以下方面的反馈:
std::variant
时的效率考虑。在使用
std::variant
进行错误处理时,我将不胜感激任何见解或最佳实践示例。
以这种方式使用
时的效率考虑。std::variant
因为您必须在快乐路径上进行访问,所以在许多平台上,它的性能会低于使用异常,而在不抛出异常时,开销可能接近于零。
任何潜在的陷阱或可以对此模式进行的改进。
std::string
是相当常见的类型。如果您的字符串操作可能会失败,会发生什么?
您可能想创建一个特定的
Error
类型,即使它只是
struct Error {
std::string what;
};
将
int
上的操作“提升”为 Result
类型也很好:
template <typename UnaryOperation>
Result transform(UnaryOperation op, Result input) -> Result {
return std::visit([](auto&& arg) {
using T = decltype(arg);
if constexpr (std::is_same_v<std::decay_t<T>, Error>) {
return std::forward<T>(arg);
} else {
return op(std::forward<T>(arg));
}, input);
}
template <typename BinaryOperation>
Result transform(BinaryOperation op, Result lhs, Result rhs) -> Result {
return std::visit([](auto&& left, auto&& right) {
using L = decltype(left);
using R = decltype(right);
if constexpr (std::is_same_v<std::decay_t<L>, Error> && std::is_same_v<std::decay_t<R>, Error>) {
return Error{ std::forward<L>(left).what + std::forward<R>(right).what };
} else if constexpr (std::is_same_v<std::decay_t<L>, Error>){
return std::forward<L>(left);
} else if constexpr (std::is_same_v<std::decay_t<R>, Error>){
return std::forward<R>(right);
} else {
return op(std::forward<L>(left), std::forward<R>(right));
}, lhs, rhs);
}