向std::shared_ptr管理的vector添加元素引发段错误的原因与解决
你的代码触发段错误的核心原因是在遍历vector的过程中修改容器(执行push_back)导致迭代器失效,具体拆解如下:
为什么会触发Segmentation Fault?
你在main函数中使用范围for循环遍历simple_objects中的元素:
for (auto &obj: *simple_objects) { obj.grow(); }
范围for循环的底层实现依赖于vector的迭代器——它会先调用begin()获取起始迭代器,然后每次循环递增迭代器,直到到达end()。
而grow()方法中执行了objects->push_back(SimpleClass(objects)):当vector的剩余容量不足以容纳新元素时,它会重新分配一块更大的内存空间,把原有的所有元素拷贝/移动到新内存,然后释放旧内存。这个操作会导致所有指向原vector元素的迭代器、指针、引用全部失效。
当第一个obj.grow()执行多次push_back后,vector触发扩容,此时范围for循环持有的迭代器已经变成了指向已释放内存的“野指针”,后续循环中尝试通过这个失效的迭代器访问元素时,就会触发段错误。
顺带提一句:你的SimpleClass中用shared_ptr持有vector的设计本身是没问题的——shared_ptr的拷贝会正确维护引用计数,不会导致vector被提前释放,问题不在这部分。
如何避免这个问题?
有几种不同的解决方案,你可以根据需求选择:
方案1:提前预留足够的容器容量
如果能预估到最终需要的元素数量,可以提前调用reserve()为vector分配足够的内存,避免扩容操作:
int main () { auto simple_objects = std::make_shared<std::vector<SimpleClass>>(); // 预估需要的总容量:初始10个 + 每个元素添加10个 = 110 simple_objects->reserve(110); for (int i { 0 }; i < 10; ++i) { simple_objects->push_back(SimpleClass { simple_objects }); } std::cout << "simple_objects size: " << simple_objects->size() << std::endl; for (auto &obj: *simple_objects) { obj.grow(); } return 0; }
这种方法简单直接,但缺点是如果后续元素数量超出预估,还是会触发扩容和迭代器失效。
方案2:遍历临时副本,避免修改原容器时影响遍历
先把原vector的元素拷贝到一个临时容器中,然后遍历这个临时容器执行grow()操作。这样原容器的扩容不会影响遍历过程:
int main () { auto simple_objects = std::make_shared<std::vector<SimpleClass>>(); for (int i { 0 }; i < 10; ++i) { simple_objects->push_back(SimpleClass { simple_objects }); } std::cout << "simple_objects size: " << simple_objects->size() << std::endl; // 创建临时副本,遍历副本执行操作 auto temp_objects = *simple_objects; for (auto &obj : temp_objects) { obj.grow(); } return 0; }
这种方法更灵活,不需要预估容量,缺点是会产生一次元素拷贝(如果SimpleClass比较大,会有性能开销,但你的类里只有一个shared_ptr,拷贝成本很低)。
方案3:改用不会因插入失效迭代器的容器
如果你的场景经常需要在遍历过程中修改容器,可以把vector换成std::list——list的push_back操作不会导致现有迭代器失效(除了end()迭代器)。不过需要注意list的随机访问性能不如vector,如果你的代码依赖随机访问,这个方案可能不适用。
额外提示
在C++中,修改容器(插入/删除元素)时一定要注意迭代器的有效性规则,不同容器的规则不同:
vector/string:插入元素可能导致所有迭代器失效(当扩容时);删除元素会导致删除位置及之后的迭代器失效。list/forward_list:插入/删除元素只会导致指向被操作元素的迭代器失效,其他迭代器不受影响。map/set:插入/删除元素只会导致指向被操作元素的迭代器失效,其他迭代器不受影响。
内容的提问来源于stack exchange,提问作者pistacchio

