unordered_map元素被删除

时间:2016-08-09 15:00:18

标签: c++ c++11 unordered-map

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

这是我的插入内容:

my_umap.insert(std::make_pair(key.toString(), my_struct));

struct包含一个记录插入时间的成员。然后我定期检查地图并删除已经在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);
    }
}

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

  

无法访问内存

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

如果unordered_map的大小为零,for循环如何检测元素?没有其他线程访问此容器。我以为unordered_map::insert()将对象复制到容器中,因此被删除的原始对象不重要吗?

4 个答案:

答案 0 :(得分: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.
    }
}

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);
}

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

答案 1 :(得分:8)

erase()会使您正在删除的迭代器失效。当您随后在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;
    }
}

就个人而言,我更喜欢it = map.erase(it)map.erase(it++),而是YMMV。

我只是将它包装在一个功能模板中,这样你就不必继续改写这类东西:

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;
        }
    }
}

然后:

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

答案 2 :(得分:0)

实际上,@ barry建议的erase_if()已经是TS Library Fundamentals V2(Uniform Container Erasure)的一部分。 如果它成为C ++ 17的一部分,我现在无法找到。

这是一个参考: http://en.cppreference.com/w/cpp/experimental/unordered_map/erase_if

Visual Studio 2015附带的标准库已经实现了它。

libstdc++(gcc附带的标准库)的状态页面我们可以看到它也实现了它,但它只在开发版本中,而不是在任何特定版本中。 (https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html#status.iso.201z

请注意,与大多数当前算法不同,这些函数存在于相关容器的标头中(不在算法标头中),并且它们引用容器本身(而不是迭代器对)。这些更改是因为这些函数必须知道容器才能正确实现循环并使用容器成员函数erase()。 因此,如果您只想在容器中的特定范围内应用此擦除操作,您仍然必须使用其他答案中描述的手写循环(可能Ranges TS也改进了这一点吗?)。

答案 3 :(得分:-1)

问题是你搞乱了迭代器。当您在迭代过程中擦除元素时,将擦除应该导致下一个迭代器的当前迭代器。

有几种方法可以解决这个问题。第一个是我刚从c ++参考中复制的东西:

int main()
{
    std::map<int, std::string> c = { { 1, "one" }, { 2, "two" }, { 3,     "three" },
    { 4, "four" }, { 5, "five" }, { 6, "six" } };
    // erase all odd numbers from c
    for (auto it = c.begin(); it != c.end();)
        if (it->first % 2 == 1)
            it = c.erase(it);
        else
            ++it;
    for (auto& p : c)
        std::cout << p.second << ' ';

注意循环。它不会推进迭代器。相反,它将迭代器分配给从擦除元素返回的迭代器 - 擦除返回下一个迭代器,或者,如果没有擦除,则显式推进它。

解决此问题的第二个选项是我对第一个程序所做的以下更改:

int main()
{
    std::map<int, std::string> c = { { 1, "one" }, { 2, "two" }, { 3, "three" },
{ 4, "four" }, { 5, "five" }, { 6, "six" } };

    // collect all odd numbers from c into a vector
    vector<int> to_delete;
    for (auto pair : c) if (pair.first % 2 == 1) to_delete.push_back(pair.first); 

    // now delete them all
    for (auto k : to_delete) c.erase(k);

    for (auto& p : c)
        std::cout << p.second << ' ';
}

这次我收集矢量中的所有键,然后扫描矢量并从地图中删除每个键。这样,您在迭代地图时不会从地图上删除。