You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

类内存储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

问题现象

  1. 调用m_cursorList.erase(m_cursorMap[pos]);后,列表大小未按预期变更,多次调用函数后m_cursorList的元素地址出现无规律变动。
  2. 被存储在m_cursorMap中的迭代器虽能正常解引用,但实际已失效,触发未定义行为。
  3. 相同逻辑在函数内作为局部变量实现时正常,移为类成员变量后出现上述问题。

现象是否正常?

这种现象不正常,属于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:55:22