简体   繁体   中英

Erasing elements from unordered_map in a loop

There are several answers on StackOverflow that suggest that the following loop is a fine way to erase elements from a std::unordered_map that satisfy some predicate pred :

std::unordered_map<...> m;
auto it = m.begin();
while (it != m.end())
{
    if (pred(*it))
        it = m.erase(it);
    else
        ++it;
}

I'm specifically interested in C++11 (as opposed to C++14), and the following ominous note on cppreference.com suggests that the above loop depends on undefined behavior and may not work in C++11 after all:

The order of the elements that are not erased is preserved (this makes it possible to erase individual elements while iterating through the container) (since C++14)

See also Title 2356. Stability of erasure in unordered associative containers which contains a requested wording change to Working Draft N3797 item 14 on page 754 (the additional phrase beginning ", and preserve the relative order ...").

This wording is relative to N3797.

Modify [unord.req], p14 as indicated:

-14- The insert and emplace members shall not affect the validity of references to container elements, but may invalidate all iterators to the container. The erase members shall invalidate only iterators and references to the erased elements, and preserve the relative order of the elements that are not erased.

If my interpretation of the note from cppreference.com is correct, and the above loop depends on undefined behavior in C++11, what's the most efficient way to solve this problem in C++11?

To comply with C++11 you're unfortunately a bit limited in how you can tackle this. Your options basically boil down to:

  1. Iterate over the unordered_map and build a list of keys to delete like so:

     //std::unordered_map<...> mymap; std::vector<decltype(mymap)::key_type> vec; for (auto&& i : mymap) if (/*compare i*/) vec.emplace_back(i.first); for (auto&& key : vec) mymap.erase(key);
  2. Iterate over the object and reset if we find something to remove - I'd really only recommend this for small datasets. those of you who feel goto is unconditionally bad, well, this option is arguably bad.

     //std::unordered_map<...> mymap; reset: for (auto&& i : mymap) if (/*compare i*/) { mymap.erase(i.first); goto reset; }
  3. As a somewhat out there option, you could also just create a new unordered_map and move the elements that you want to keep. This is arguably a good option when you have more to delete than to keep.

     //std::unordered_map<...> mymap; decltype(mymap) newmap; for (auto&& i : mymap) if (/*i is an element we want*/) newmap.emplace(std::move(i)); mymap.swap(newmap);

Well, you can always do this:

std::unordered_map<...> m;
std::vector<key_type> needs_removing;
for(auto&& pair : m)
{
    if (pred(pair))
        needs_removing.push_back(pair.first);
}
for(auto&& key : needs_removing)
    m.erase(key);

It's slower, but the complexity is the same.

Use erase_if (c++20) instead of a loop (see https://en.cppreference.com/w/cpp/container/unordered_map/erase_if )

Example for removing odd keys from a map:

std::unordered_map<int, char> data {{1, 'a'},{2, 'b'},{3, 'c'},{4, 'd'},
                                    {5, 'e'},{4, 'f'},{5, 'g'},{5, 'g'}};

const auto count = std::erase_if(data, [](const auto& item) {
    auto const& [key, value] = item;
    return (key & 1) == 1;
});

Always consult the stl-algorithms first

This one seems to be the wanted: http://www.cplusplus.com/reference/algorithm/remove_if/

For an overview: http://www.cplusplus.com/reference/algorithm/

EDIT cppreference has an similar example at the bottom of the site. It works with a c++11 compiler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM