类内存储std::list迭代器的std::map失效问题及替代方案咨询
问题分析与解决方案
类私有成员定义
private: std::list<QTextCursor> m_cursorList; std::map<int, std::list<QTextCursor>::iterator> m_cursorMap;
addCursor函数实现
void addCursor(QTextCursor cursor) { if (cursor.isNull()) return; QTextCursor c1 = cursor; const int pos = c1.position(); qDebug() << " "; qDebug() << "pos" << pos; if (m_cursorMap.find(pos) != m_cursorMap.end()) { qDebug() << "erasing" << &(*m_cursorMap[pos]); qDebug() << "size before" << m_cursorList.size(); const auto c2 = *m_cursorMap[pos]; for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) qDebug() << "before erase" << &(*it); m_cursorList.erase(m_cursorMap[pos]); for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) qDebug() << "after erase" << &(*it); qDebug() << "size after" << m_cursorList.size(); m_cursorMap.erase(pos); } const auto itc = m_cursorList.insert(m_cursorList.end(), c1); const auto itc2 = --m_cursorList.end(); m_cursorMap[pos] = itc; qDebug() << "inserting" << &(*itc) << &(*itc2) << m_cursorList.size(); for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) qDebug() << "after inserting" << &(*it); }
运行日志
pos 0 inserting 0x55d774127fc0 0x55d774127fc0 1 after inserting 0x55d774127fc0 pos 0 erasing 0x55d774127fc0 size before 1 before erase 0x55d77408b4e0 after erase 0x55d77408b4e0 size after 0 inserting 0x55d774127fc0 0x55d774127fc0 1 after inserting 0x55d77408b4e0 after inserting 0x55d774127fc0 pos 0 erasing 0x55d774127fc0 size before 2 before erase 0x55d7740fd440 before erase 0x55d7740fd420 after erase 0x55d7740fd440 after erase 0x55d7740fd420 size after 1 inserting 0x55d774127fc0 0x55d774127fc0 2 after inserting 0x55d7740fd440 after inserting 0x55d7740fd420 after inserting 0x55d774127fc0 pos 317914 inserting 0x55d774bd0fa0 0x55d774bd0fa0 4 after inserting 0x55d774ba32e0 after inserting 0x55d774ba38c0 after inserting 0x55d774ba38e0 after inserting 0x55d774bd0fa0 pos 0 erasing 0x55d774127fc0 size before 4 before erase 0x55d77408b4e0 before erase 0x55d7740fcf40 before erase 0x55d7740fd520 before erase 0x55d774b9d4f0
问题现象
- 调用
m_cursorList.erase(m_cursorMap[pos]);后,列表大小未按预期变更,多次调用函数后m_cursorList的元素地址出现无规律变动。 - 被存储在
m_cursorMap中的迭代器虽能正常解引用,但实际已失效,触发未定义行为。 - 相同逻辑在函数内作为局部变量实现时正常,移为类成员变量后出现上述问题。
现象是否正常?
这种现象不正常,属于C++标准定义的未定义行为。
std::list的erase操作只会使被删除元素的迭代器失效,其他元素的迭代器、指针、引用都应保持有效。出现size不更新、地址全变的情况,本质是调用erase时传入的迭代器已经提前失效。
局部变量正常、类成员异常的核心原因:
- 局部变量生命周期短,没有外部代码(如类的其他成员函数)对其修改,迭代器不会提前失效;
- 类成员变量可能被其他线程、其他成员函数操作,导致
m_cursorMap中存储的迭代器与m_cursorList的实际状态脱节,变成失效迭代器。
无需迭代器的替代实现方式
方案1:直接用std::map存储(推荐,复杂度O(logn))
如果核心需求是按position唯一存储QTextCursor,std::map本身可保证key唯一性,完全不需要额外的list:
private: std::map<int, QTextCursor> m_cursorMap; void addCursor(QTextCursor cursor) { if (cursor.isNull()) return; const int pos = cursor.position(); // 直接赋值,自动覆盖同pos的旧cursor m_cursorMap[pos] = cursor; }
方案2:用std::list存储键值对(保留插入顺序,复杂度O(n))
如果需要保持元素插入顺序,可以用std::list存储<pos, cursor>键值对,每次添加时遍历查找并删除旧值:
#include <algorithm> // 需要包含头文件 private: std::list<std::pair<int, QTextCursor>> m_cursorList; void addCursor(QTextCursor cursor) { if (cursor.isNull()) return; const int pos = cursor.position(); // 查找并删除同pos的元素 auto it = std::find_if(m_cursorList.begin(), m_cursorList.end(), [pos](const std::pair<int, QTextCursor>& elem) { return elem.first == pos; }); if (it != m_cursorList.end()) { m_cursorList.erase(it); } // 插入新元素到末尾 m_cursorList.emplace_back(pos, cursor); }
这种方式虽查找为线性复杂度,但元素数量不多时性能完全可接受,且彻底避免了迭代器失效问题。
内容的提问来源于stack exchange,提问作者pointHex
相关产品推荐
相关产品推荐

