直接用 yaml-cpp(v0.8.0+且启用YAML_CPP_BUILD_CONTRIB=ON),它封装libyaml、提供C++ API、支持注释保留与类型推导;libyaml为纯C,需手动处理事件流和注释,工程成本高。

YAML 解析器选 yaml-cpp 还是 libyaml?
直接用 yaml-cpp。它封装了 libyaml 的 C 接口,提供 C++ 风格的 API,支持注释保留(从 v0.8.0 起)、类型推导和异常友好;而原生 libyaml 是纯 C,需手动管理事件流,读注释得自己解析 token,工程成本高得多。
注意:v0.7.x 及更早版本的 yaml-cpp 不保留注释——必须升级到 v0.8.0+,且启用构建选项 YAML_CPP_BUILD_CONTRIB=ON(否则 Node::Mark() 和注释访问接口不可用)。
- Ubuntu/Debian 下 apt 安装的
libyaml-cpp-dev通常是 v0.6.x,不满足要求 - 推荐用 vcpkg 或 conan 拉取最新版,或从源码编译:
cmake -DYAML_CPP_BUILD_CONTRIB=ON .. - 注释不是“自动附着在键上”的——它属于某个
Node的Mark位置,需结合Node::size()和遍历顺序手动对齐
怎么读取文件并保留注释信息?
yaml-cpp 默认解析会丢弃所有注释。要拿到注释,必须用 YAML::LoadFile() 后立即调用 node.Mark() 获取行号,再配合 YAML::Parser + YAML::Scanner 手动提取原始文本中的注释行——但这样太重。
更轻量的做法是:改用 YAML::LoadInput 加自定义 YAML::Input,并确保传入的是带 BOM 或 UTF-8 编码的完整字符串(而非直接文件路径),再启用贡献模块后,通过 node.GetMark().input_pos_ 和底层缓冲区反查注释行。不过实践中,90% 的配置场景只需“跳过注释、正确解析结构”,此时只需确认 YAML::LoadFile() 不报错即可。
立即学习“C++免费学习笔记(深入)”;
- 真正需要注释内容时(比如生成带注释的回写配置),必须用
YAML::Node::size()遍历所有子节点,对每个key调用node[key].Mark().line并比对前后行是否有#开头的行 - Windows 下用
std::ifstream读文件时记得加.imbue(std::locale::classic()),否则宽字符/换行符可能错位,导致Mark().line偏移不准 - 不要依赖
node["key"].IsDefined()判断字段是否存在——它对注释后的空 key 也返回 true;应配合node["key"].IsNull()一起看
YAML::BadConversion 错误常见于哪些操作?
这个异常基本只发生在用 as<T>() 强转类型失败时,比如把 "true" 字符串当 bool 读、把 "123abc" 当 int 读,或者把缩进错位的块序列当成 map 解析。
- 避免方式:优先用
as<T>()前加IsScalar()/IsSequence()/IsMap()类型检查 - 更安全的写法是用
node["port"].as<int>(8080)提供默认值,而不是裸调as<int>() - 浮点数字段若写成
timeout: 3.5e-2,as<double>()可正常解析;但写成timeout: .5(缺整数位)会触发BadConversion - 嵌套层级过深(>100 层)也可能触发该异常——不是类型问题,而是解析器递归限制,需调大
YAML::MaxRecursionDepth(200)
为什么修改配置后用 YAML::Emitter 回写会丢注释?
YAML::Emitter 是纯输出器,不持有任何输入源信息,它只按当前 Node 结构生成 YAML 文本,不关心原始文件里哪行有注释、哪行是空行。这是设计使然,不是 bug。
如果业务真需要“编辑后保留原有注释”,就得放弃 Emitter,改用字符串替换方案:先用正则提取所有 key: value 行及其紧邻的上一行注释,再按新值替换对应行。简单但脆弱;稳妥做法是用 yaml-cpp v0.8+ 的 YAML::detail::node_data 内部结构(不推荐,属未公开 API)或换用 Python 的 ruamel.yaml 做预处理。
- 轻量级项目中,建议接受“回写不带注释”这一事实,把注释当作文档,只在源配置模板中维护
- 若必须保留,可将注释提前提取到 map 中:
std::map<:string std::string> comments;</:string>,key 为字段名,value 为注释内容,回写时手动拼接 - 别试图用
node.Mark()的列号去定位注释——YAML 允许注释出现在行末,列号无法唯一映射到某条注释
LoadFile() 调用就搞定。



















