在C++中能否将迭代器存储在结构体中?若不可行,原因是什么?
我需要从多个JSON文件读取数据,打算在初始化完成后再执行实际读取操作。出于特定原因,我认为把每个JSON文件首个元素对应的迭代器存储起来会很有用,这样后续就能遍历每个JSON文件并读取第i个元素。
但遇到了问题:如果创建迭代器后立即用它解析数据,一切正常;但把迭代器存储到结构体中,后续再使用时会出现读访问违例。以下是相关代码片段:
首先是存储单个JSON文件信息的结构体声明:
typedef struct _struct { ... boost::property_tree::ptree json_struct; boost::property_tree::ptree::iterator it; ... } json_handler;
然后是存储多个handler的容器:
std::map<std::string, json_handler> mjh;
这段代码可以正常工作:
json_handler cms; boost::property_tree::read_json(full_path, cms.json_struct); boost::property_tree::ptree &data = cms.json_struct.get_child("frames"); // 如果在此处直接遍历JSON内容,一切正常 for (boost::property_tree::ptree::iterator element = data.begin(); element != data.end(); element++) double time = element->second.get<double>("ST"); // 将当前json_handler插入到map中 mjh.insert(std::pair<std::string,json_handler>(name, cms));
但这段后续使用数据的代码无法正常工作:
// 后续使用数据的代码 std::map<std::string, json_handler>::iterator it; for (it = mjh.begin(); it != mjh.end(); it++) { double _st = it->second.it->second.get<double>("ST"); std::cout << "ST: " << std::to_string(_st ) << std::endl; ... }
请问:能否将迭代器存储在结构体中?若不可行,原因是什么?
当然可以存储迭代器,但你的问题出在迭代器失效上,具体原因如下:
当你执行mjh.insert(std::pair<std::string,json_handler>(name, cms))时,你把cms这个json_handler对象拷贝到了std::map中。而你之前存在cms.it里的迭代器,是指向cms.json_struct.get_child("frames")这个子树的内部数据的——这个子树属于原来的cms对象。
一旦拷贝完成,map里的json_handler是一个完全独立的副本:它的json_struct拥有自己的内存空间,和原来的cms没有任何关系。而你存储的迭代器依然指向原来cms对象的frames子树内存。如果原来的cms是局部变量,之后会被销毁,那这片内存就变成了无效内存;就算cms还存在,你用这个迭代器去访问map里的副本对象的json_struct,本质上是在访问错误的内存区域,自然会触发读访问违例。
Boost Property Tree的迭代器和STL容器迭代器的特性类似:它们绑定到特定的容器实例,当容器被拷贝、移动或者销毁时,原来的迭代器就会失效,无法再用于访问新的容器实例。
解决办法
有几种可行的方案来修复这个问题:
1. 使用智能指针存储json_handler,避免拷贝
把std::map的 value 类型改成智能指针,这样就不会拷贝整个json_handler对象,只会传递指针,迭代器指向的还是同一个ptree实例:
// 修改容器类型 std::map<std::string, std::unique_ptr<json_handler>> mjh; // 创建并初始化handler auto cms = std::make_unique<json_handler>(); boost::property_tree::read_json(full_path, cms->json_struct); boost::property_tree::ptree &data = cms->json_struct.get_child("frames"); cms->it = data.begin(); // 插入到map中(移动语义,避免拷贝) mjh.emplace(name, std::move(cms)); // 后续使用时 for (auto &entry : mjh) { double _st = entry.second->it->second.get<double>("ST"); std::cout << "ST: " << std::to_string(_st) << std::endl; }
2. 不存储迭代器,每次需要时重新获取
如果你的场景允许,也可以不存储迭代器,每次需要访问时重新获取frames子树的迭代器:
// 修改结构体,去掉存储的迭代器 typedef struct _struct { ... boost::property_tree::ptree json_struct; ... } json_handler; // 后续使用时 for (auto &entry : mjh) { auto &frames = entry.second.json_struct.get_child("frames"); if (!frames.empty()) { double _st = frames.begin()->second.get<double>("ST"); std::cout << "ST: " << std::to_string(_st) << std::endl; } }
这种方法虽然每次都要调用get_child和begin(),但胜在简单安全,不会有迭代器失效的问题。
3. 存储frames子树的副本
如果整个JSON里你只关心frames部分,也可以直接在结构体里存储frames子树的副本,而不是整个JSON结构,这样迭代器指向的就是结构体内部的子树,拷贝后也依然有效:
typedef struct _struct { ... boost::property_tree::ptree frames; boost::property_tree::ptree::iterator it; ... } json_handler; // 初始化时 json_handler cms; boost::property_tree::ptree temp_json; boost::property_tree::read_json(full_path, temp_json); cms.frames = temp_json.get_child("frames"); cms.it = cms.frames.begin(); mjh.insert(std::pair<std::string,json_handler>(name, cms));
这样拷贝json_handler时,frames和迭代器都会被正确拷贝,迭代器指向的是副本里的frames子树,访问就不会出错了。
内容的提问来源于stack exchange,提问作者Alexis Nicole

