不该直接放std::error_code或std::string;应使用std::variant<ParseError, std::string>等组合,其中ParseError含错误种类、行号和上下文视图,避免语义模糊与构造开销。

std::variant里该不该放std::error_code或std::string?
不该直接放裸错误类型——std::variant<int std::string></int>看似能存成功值和错误信息,但语义模糊:你无法区分“返回空字符串是业务数据”还是“解析失败”。更糟的是,std::string构造开销大,且没携带错误上下文(比如行号、字段名)。
推荐组合:std::variant<:monostate int std::error_code></:monostate> 或 std::variant<:monostate data parseerror></:monostate>,其中ParseError是自定义结构体,含enum class kind、int line、std::string_view context。
-
std::monostate必须显式放在类型列表最前,否则默认构造会尝试构造第二个类型(可能失败) - 避免用
std::exception_ptr——它不 trivially copyable,移动开销大,且std::visit处理时需额外std::rethrow_exception调用 - 若错误需携带堆分配资源(如详细trace),用
std::unique_ptr<parseerror></parseerror>,但确保析构安全,别让variant生命周期短于其内部指针
std::visit处理错误分支时,lambda参数const限定错会编译失败
常见错误:写std::visit([](ParseError& e) { ... }, v),但v是const std::variant<...>&</...>。编译器找不到匹配重载,报no matching function for call to 'visit',而不是告诉你“参数应该加const&”。
正确写法统一用const auto&或显式写出每个类型的const&:
立即学习“C++免费学习笔记(深入)”;
std::visit([](const auto& x) {
if constexpr (std::is_same_v<decltype(x), const std::monostate&>) {
// 未初始化状态,通常表示解析前就出错(如buffer为空)
} else if constexpr (std::is_same_v<decltype(x), const ParseError&>) {
std::cerr << "Parse failed at line " << x.line << ": " << x.context << "\n";
} else {
// 正常数据
process(x);
}
}, v);- 用
if constexpr避免运行时分支,编译期剔除不匹配路径 - 别漏
std::monostate分支——它代表“还没成功构造任何有效类型”,不是错误,而是初始空态 - 若
v是右值(如函数返回的临时variant),auto&&比const auto&更稳妥,避免绑定到临时对象失败
std::get_if比std::get更适合错误场景
当你只关心是否出错、且不想抛异常时,std::get_if<ParseError>(&v)返回ParseError*,空指针表示当前不是错误类型;而std::get<ParseError>(v)一旦类型不对就炸——std::bad_variant_access。
典型使用模式:
if (auto* err = std::get_if<ParseError>(&v)) {
handle_error(*err);
return;
}
// 此时可安全std::get<Data>(v),因为已排除错误分支-
std::get_if不抛异常,适合嵌入在性能敏感路径中(如网络包循环解析) - 注意:
std::get_if<std::monostate>(&v)永远返回非空指针——只要v处于valueless_by_exception状态(比如构造时抛异常),std::get_if<std::monostate>也返回nullptr,得用v.valueless_by_exception()单独判断 - 别混用
std::holds_alternative<ParseError>(v)和std::get<ParseError>(v)——两次类型检查冗余,且前者为true时后者才安全,不如直接std::get_if
解析中途失败时,variant可能进入valueless_by_exception状态
这是最容易被忽略的坑:std::variant在构造某个类型时抛异常(比如Data构造函数里throw std::runtime_error),整个variant会变成valueless_by_exception(),此时std::holds_alternative对所有类型都返回false,std::get_if全返回nullptr,std::visit调用直接std::terminate。
- 必须在访问前检查:
if (v.valueless_by_exception()) { /* 记录日志并跳过 */ } - 避免在
variant成员类型构造函数里抛异常——改用static std::optional<T> parse(...)接口,让解析逻辑自己处理失败,返回std::nullopt,再由外层决定塞std::monostate还是ParseError -
std::variant本身不提供reset()方法,清空只能赋值std::monostate{}(前提是std::monostate在类型列表里)
解析错误处理的核心不在怎么存,而在怎么让错误状态不可绕过——std::variant强制你面对每一种可能,包括空、错、对。漏掉valueless_by_exception或std::monostate,就是给崩溃留门。


















