unordered_map元素被删除

use*_*112 8 c++ unordered-map c++11

我正在将一个{string, MyStruct}对象插入到unordered_map中,稍后迭代unordered_map并选择擦除该元素.但是,在擦除元素之前,我有一个断言,它显示unordered_map为空.

这是我的插入内容:

my_umap.insert(std::make_pair(key.toString(), my_struct));
Run Code Online (Sandbox Code Playgroud)

该结构包含一个记录插入时间的成员.然后我定期检查地图并删除已经在unordered_map中的元素太长时间:

for(auto it = my_umap.begin(); it != my_umap.end(); ++it){

    MyStruct& myStruct = it->second;
    const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));

    if(deleteEntry){
        const string& key = it->first;    // Cannot access memory for 'key'
        assert(my_umap.size() >= 1);       // This is failing
        my_umap.erase(key);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在gdb中运行代码,断言失败.当我查询key它的值时说

无法访问内存

当我查询my_umap它的大小时,表示大小为零.

如果unordered_map的大小为零,for循环如何检测元素?没有其他线程访问此容器.我想unordered_map::insert()将对象复制到容器中,所以删除的原始对象应该无关紧要?

Kar*_*oll 11

调用后my_umap.erase(...),迭代器变为无效:

cppreference.com说:

擦除元素的引用和迭代器无效.其他迭代器和引用不会失效.

这意味着一旦项被擦除,指向它的迭代器就不再有效.

你有几个选择:

1.使用迭代器擦除,并使用返回值 erase()

从C++ 11开始,迭代器擦除将返回一个迭代器,指向地图中的下一个项目.所以你可以使用它来保持你的迭代器有效:

auto it = my_umap.begin();

while (it != my_umap.end()) {

    MyStruct& myStruct = it->second;
    const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));

    if(deleteEntry){
        assert(my_umap.size() >= 1);
        it = my_umap.erase(it);  // <-- Return value should be a valid iterator.
    }
    else{
        ++it;  // Have to manually increment.
    }
}
Run Code Online (Sandbox Code Playgroud)

2.将迭代器存储在列表对象中,并在迭代后擦除.

或者,您可以将删除候选项存储在列表对象中(例如,向量并在初始迭代后删除它们:

std::vector<MapType::iterator> deleteCandidates;

for(auto it = my_umap.begin(); it != my_umap.end(); ++it){

    MyStruct& myStruct = it->second;
    const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));

    if(deleteEntry)
        deleteCandidates.push_back(it);
}

for (auto it : deleteCandidates) {
    my_umap.erase(it);
}
Run Code Online (Sandbox Code Playgroud)

至于你的断言失败的原因,你可能通过访问无效的迭代器遇到未定义的行为,使你的for循环认为地图仍然不是空的(因为invalidIterator != my_umap.end()).


Bar*_*rry 8

erase()invalides你正在擦除的迭代器.当您随后在for循环中递增它时,您将获得未定义的行为.在assert()很可能触发你的循环的第二次迭代,而不是你第一次.

你必须重新调整你的循环,如:

for(auto it = my_umap.begin(); it != my_umap.end(); /* nothing */){

    MyStruct& myStruct = it->second;
    const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));

    if(deleteEntry) {
        // either use the return
        it = my_umap.erase(it); // NB: erase by it, not by key, why
                                // do an extra lookup?       

        // or post-increment
        my_umap.erase(it++);
    }
    else {
        ++it;
    }
}
Run Code Online (Sandbox Code Playgroud)

就个人而言,我更喜欢it = map.erase(it)map.erase(it++),但情况因人而异.

我只是将它包装在一个函数模板中,这样你就不必继续重写这种东西了:

template <class Container, class F>
void erase_if(Container& c, F&& f) {
    for (auto it = c.begin(); it != c.end(); ) {
        if (f(*it)) {
            it = c.erase(it);
        }
        else {
            ++it;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

erase_if(my_umap, [](const auto& pr){
    MyStruct& myStruct = pr.second; 
    return myStruct.ts.IsElapsed(std::chrono::seconds(5));
});
Run Code Online (Sandbox Code Playgroud)