TinyXML2是中小型配置类XML最省心的选择,因其零依赖、双文件、UTF-8原生支持及直白API;但需严防空指针(如避免直接调用RootElement())、手动处理路径/BOM/编码,并始终判空后再用GetText()。

tinyxml2 是当前最省心的选择,尤其对中小型配置类 XML。它不依赖第三方库、头文件+源码仅两个文件、全平台 UTF-8 原生支持,且 API 直白——只要避开几个关键坑,基本不会崩。
为什么别用 RootElement() 而要用 FirstChildElement()
很多崩溃就发生在这一步:XMLDocument::RootElement() 在文档为空或解析失败时返回 nullptr,但开发者常直接链式调用 ->FirstChildElement(),结果解引用空指针。
-
doc.RootElement()只在文档有根元素且加载成功时才非空;但即使LoadFile()返回XML_SUCCESS,XML 语法错误(如注释含非法字符)也可能导致根节点为nullptr - 更安全的写法是:
auto root = doc.FirstChildElement("config");—— 它跳过文本节点、注释、声明等非元素节点,直接找第一个匹配标签名的元素,语义更明确 - 如果不确定根元素名,先用
doc.FirstChildElement()(无参数)拿到第一个元素,再用Value()查看标签名
GetText() 返回 nullptr 的真实原因
不是“没内容”,而是三种常见情况都让它返回空指针:元素内纯空白(换行/缩进)、元素为空标签(<name></name>)、或根本没子节点(只有属性)。直接解引用必崩。
- 必须先判空:
if (const char* text = elem->GetText()) { std::string_view sv{text}; /* 再 trim */ } - trim 不能靠
std::string::erase()原地删,推荐用std::string_view(text).find_first_not_of(" \t\n\r")定位起始,再find_last_not_of()定位结尾 - 若需合并所有子文本(比如含 CDATA 或混合了文本+注释),得手动遍历:
for (auto node = elem->FirstChild(); node; node = node->NextSibling()) { if (node->ToText()) { /* 处理 */ } }
路径、编码、BOM 这三件事必须人工盯死
LoadFile() 成功 ≠ 文件真被正确读入。Windows 下尤其容易栽在这三处。
立即学习“C++免费学习笔记(深入)”;
- 相对路径行为不一致:IDE 工作目录常是项目根,命令行可能是可执行文件所在目录。建议调试期一律用绝对路径,或用
std::filesystem::current_path()打印确认 - 编码必须是 UTF-8(不含 BOM):TinyXML2 能自动跳过 UTF-8 BOM(
0xEF 0xBB 0xBF),但若你用std::ifstream读取后传给Parse(),就得自己截掉前三个字节,否则解析卡在开头 - 别信 XML 声明里的
encoding="GBK":TinyXML2 不做编码转换,遇到非 UTF-8 声明会静默失败或乱码。要么提前转成 UTF-8 存储,要么用iconv预处理 buffer
生成 XML 时缩进和中文输出怎么不出错
默认 SaveFile() 输出是单行紧凑格式,且不保证中文可读——因为 SetText() 接收的是 raw 字节,你传 GBK 字符串进去,它就原样写。
- 缩进必须显式设置:
doc.SetIndent(" ");要在SaveFile()之前调用,且只对本次保存生效 - 中文文本必须确保是合法 UTF-8 字节序列:用
std::string构造时,确认源字符串本身是 UTF-8 编码(例如 VS 项目设为“UTF-8 无签名”,或 Linux 下 locale 是en_US.UTF-8) - 如果要插入带引号或
的文本,别手拼:<code>element->SetText("a 会自动转义;手动拼 <code>"a < b"反而会被二次转义
真正麻烦的从来不是“怎么写”,而是“哪一步没检查 null”“路径在哪变了”“BOM 藏在哪了”。这些点不卡住,tinyxml2 就真的只是几行代码的事。


















