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

迭代unordered_set时元素可能被删除的优雅解决方案

问题:遍历unordered_set时删除元素导致循环提前终止

我遇到的问题最小复现版本如下:我有一个存储对象指针的unordered_set,在迭代该集合的过程中,部分对象需要被从集合中删除。在大型程序中这会导致崩溃,而在此示例中仅删除一个元素后循环就终止了。

场景是一款包含Unit的游戏:闲置的Unit会被触发执行动作,闲置Unit被记录在名为idle_units的unordered_set中。在游戏的更新循环中,程序会遍历idle_units并让其中的Unit执行act()方法,调用act()后该Unit不再处于闲置状态,会被从idle_units中移除。

复现代码

#include <vector>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <cstdlib>
#include <iostream>

// forward declarations
struct Unit;
void set_not_idle(Unit* u_);

struct Unit {  // a simple object with unique identifier
    Unit() { static int id_ = 0; id = id_++; }
    void act() { set_not_idle(this); }
    int id;
};

// data
std::vector<std::unique_ptr<Unit>> unit_storage;
std::unordered_set<Unit*> unit_ptrs;
std::unordered_map<int, Unit*> unit_from_id;
std::unordered_set<Unit*> idle_units;

Unit* get_unit_ptr(Unit u) { return unit_from_id[u.id]; }

void set_idle(Unit* u_) {
    Unit* u = get_unit_ptr(*u_); // ensure we have pointer to unit in storage
    idle_units.insert(u);
}

void set_not_idle(Unit* u_) {
    Unit* u = get_unit_ptr(*u_); // ensure we have pointer to unit in storage
    idle_units.erase(u);
}

void add_unit(Unit u_) {
    unit_storage.push_back(std::make_unique<Unit>(u_));
    Unit* u_ptr = unit_storage.back().get();
    unit_ptrs.insert(u_ptr);
    unit_from_id[u_ptr->id] = u_ptr; // set map from id to pointer in storage
    set_idle(u_ptr); // units start as idle
}

void print() {
    std::cout << "Units in storage: ";
    for (auto a : unit_ptrs) {
        std::cout << a->id << " ";
    }
    std::cout << "  Idle units: ";
    for (auto it = idle_units.begin(); it != idle_units.end(); ++it) {
        std::cout << (*it)->id << " ";
    }
    std::cout << std::endl;
}

int main() {
    srand(25);
    std::vector<Unit> units;

    // randomly populate our unit_storage with 8 units
    for (int i = 0; i < 50; i++) units.push_back(Unit());
    for (int i = 0; i < 8; ) {
        int idx = rand() % units.size();
        if (!get_unit_ptr(units[idx])) {
            add_unit(units[idx]);
            i++;
        }
    }
    print();
    // get all idle units, and have them perform an action
    for (auto it = idle_units.begin(); it != idle_units.end(); ++it) {
        (*it)->act();
    }
    print();
    return 0;
}

当前输出

Units in storage: 36 2 15 43 18 10 38 11   Idle units: 36 2 15 43 18 10 38 11 
Units in storage: 36 2 15 43 18 10 38 11   Idle units: 2 15 43 18 10 38 11 

预期结果是idle_units中无任何Unit剩余。尝试过不同迭代方式(如for (auto it : idle_units)循环、将迭代器移到循环体内等)均未解决问题,请问最优雅的解决方案是什么?


解决方案

问题核心在于:当你通过erase(u)删除元素时,当前迭代器it会直接失效,后续的++it操作会触发未定义行为(示例中表现为循环提前终止,大型程序中则可能崩溃)。

最优解决方案:提前保存下一个迭代器

在调用act()(会删除当前元素)之前,先获取下一个元素的迭代器,确保即使当前迭代器失效,我们仍有有效的指针继续遍历:

修改主函数中的循环部分:

// 安全遍历并处理所有闲置Unit
auto it = idle_units.begin();
while (it != idle_units.end()) {
    // 提前保存下一个迭代器,避免当前元素被删除后迭代器失效
    auto next_it = std::next(it);
    (*it)->act();
    it = next_it;
}

为什么原循环会失效?

在原for循环中:

for (auto it = idle_units.begin(); it != idle_units.end(); ++it) {
    (*it)->act(); // 这里删除了it指向的元素,it直接失效
}

当act()调用erase删除当前元素后,it就变成了无效迭代器,后续执行++it时程序行为未定义——在你的示例中这导致循环直接终止,而在其他场景下可能触发崩溃。

可选方案:遍历集合副本

如果不想修改遍历逻辑,也可以先复制idle_units的副本,遍历副本并处理原集合:

std::unordered_set<Unit*> idle_copy = idle_units;
for (auto unit : idle_copy) {
    unit->act();
}

这种方式逻辑简单,但缺点是当集合很大时会产生额外内存开销,适合元素数量较少的场景。


内容的提问来源于stack exchange,提问作者Apra24

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 18:35:40