yaml-cpp中YAML::LoadFile加载多文件致数据文件回滚问题咨询
问题描述
在游戏引擎中使用yaml-cpp进行场景数据序列化/反序列化时,遇到以下异常:
- 初始有两个内容完全一致的文件
A.yaml和B.yaml:entities: - entity1 - entity2 - entity3 - 对
B.yaml添加2个实体并保存后,文件内容正常更新为:entities: - entity1 - entity2 - entity3 - entity4 - entity5 - 但在同一运行时中,先加载
A.yaml,再重新加载B.yaml,B.yaml会恢复到初始状态;若保存B.yaml后重启引擎,多次加载文件则无此问题。
相关代码如下:
class scene_data { public: void save(const std::filesystem::path &) const; void load(const std::filesystem::path &); std::vector<entity_data> entities() const; void add_entity(); void delete_entity(const entity_data &); private: std::vector<entity_data> _entities; }; void scene_data::save(const std::filesystem::path &path) const { YAML::Emitter em; em << YAML::BeginMap << YAML::Key << "entities"; em << YAML::BeginSeq; for (const auto &e: _entities) { e.serialize(em);//Each entity write themselves in emitter } em << YAML::EndSeq; em << YAML::EndMap; std::ofstream filepath(path); filepath << em.c_str();//I've heard that the ofstream doesn't need to be closed manually. } void scene_data::load(const std::filesystem::path &path) { auto root = YAML::LoadFile(path.string()); if (auto ett_node = root["entities"]) { std::vector<entity_data> new_entities; for (const auto &ett: ett_node) { new_entities.push_back(entity_data(ett));//entity_data constructor takes yaml node } _entities = new_entities; } }
原因分析与解决办法
核心原因:yaml-cpp的默认文件缓存机制
yaml-cpp默认会对已加载的文件进行全局缓存,缓存键为文件路径。当同一进程中重复加载同一路径的文件时,yaml-cpp会直接返回缓存中的旧内容,而不会重新读取磁盘上的最新文件。
你的场景中,初始加载B.yaml后缓存了它的初始内容;修改保存B.yaml后,若未重启进程,后续调用YAML::LoadFile("B.yaml")依然会读取缓存的初始内容。如果此时调用save()方法,就会把缓存的初始内容重新写入文件,导致B.yaml恢复到初始状态。
解决办法
1. 禁用yaml-cpp的文件缓存
在加载文件前,通过设置全局选项禁用缓存,确保每次加载都从磁盘读取最新内容:
YAML::SetGlobalConfig(YAML::Config::DisableCache); auto root = YAML::LoadFile(path.string());
2. 绕开缓存,手动读取文件内容
不使用YAML::LoadFile,而是手动打开文件读取内容后再解析,彻底避开缓存机制:
void scene_data::load(const std::filesystem::path &path) { std::ifstream file(path); if (!file.is_open()) { // 处理文件打开失败的情况 return; } std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); auto root = YAML::Load(content); if (auto ett_node = root["entities"]) { std::vector<entity_data> new_entities; for (const auto &ett: ett_node) { new_entities.push_back(entity_data(ett)); } _entities = new_entities; } }
3. 确保文件写入完成
虽然std::ofstream会在销毁时自动关闭,但显式调用flush()可以确保所有数据都写入磁盘,避免因缓冲导致的文件内容不一致:
void scene_data::save(const std::filesystem::path &path) const { YAML::Emitter em; em << YAML::BeginMap << YAML::Key << "entities"; em << YAML::BeginSeq; for (const auto &e: _entities) { e.serialize(em); } em << YAML::EndSeq; em << YAML::EndMap; std::ofstream filepath(path); if (filepath.is_open()) { filepath << em.c_str(); filepath.flush(); // 确保数据写入磁盘 } }
额外注意:文件格式一致性
你初始的A.yaml和B.yaml是直接的序列格式(无entities键),但save()方法输出的是包含entities键的Map格式。这种格式不一致可能导致加载旧文件时_entities无法被正确赋值,建议统一文件格式,避免后续出现其他问题。
内容的提问来源于stack exchange,提问作者shklaurant
相关产品推荐
相关产品推荐

