简体   繁体   中英

Difference in iterating over value type for std::map vs std::unordered_map

The following program iterates over an unordered_map trying to find the best element but doesn't quite return the expected result:

#include <iostream>
#include <unordered_map>
using namespace std;

struct Item
{
    int val;
};

int main() {
    unordered_map<int, Item> itemMap;
    itemMap[0] = {0};
    itemMap[1] = {1};
    itemMap[2] = {2};
    itemMap[3] = {3};
    const Item* bestItem = nullptr;
    int bestVal = -1;
    for (const pair<int, Item>& item : itemMap)
    {
        if (item.second.val > bestVal)
        {
            bestVal = item.second.val;
            bestItem = &item.second;
        }
    }
    cout << "best val: " << bestVal << " best item: " << bestItem->val;
    return 0;
}

ideone

Running this program prints out:

best val: 3 best item: 0

This seems to occur because the value_type of a unordered_map is std::pair<const Key, T> but we are iterating over const pair<int, Item> . Sure enough changing this to const pair<const int, Item> results in:

best val: 3 best item: 3

However if we change the type of itemMap to std::map :

map<int, Item> itemMap

Then it doesn't matter if we iterate over const pair<int, Item> or const pair<const int, Item> we get the same result:

best val: 3 best item: 3

Even though the value_type of std::map is still std::pair<const Key, T> . But why?

The question boils down to "Why does undefined behaviour gives different results for different containers?", to which the answer is "Because it's undefined".

Less flippantly: you're probably seeing the value from the final iteration, which will be the largest in the case of an ordered map , but could be anything in an unordered_map .

From your description, I guess you understand why it's undefined: a const reference can bind to a temporary, so if the type doesn't match exactly, it will do that rather than binding to the map element itself. The reference, and hence the temporary, is scoped within the loop, so isn't available outside. Your dangling pointer to it will end up pointing to whatever has reused that memory - in this case, that's most likely the same variable in a later iteration of the loop.

As you say in a comment, use auto && (or, perhaps better, auto const & ) so that type deduction makes sure the types match and the reference binds directly to the map element. Then the pointer will point to a map element, and will still be valid outside the loop.

Both your tests (with unordered_map and map ) are exhibiting undefined behavior. The fact that one seems to work while the other doesn't is accidental, probably because they are iterated in a different order. Putting different values in the map will lead to it failing with std::map as well.

The reason the behavior is undefined: As you said the type of item is different from the value_type of the map. Because of this, a temporary of type pair<int, Item> is created and item (which is a const reference) is bound to this temporary. The lifetime of the temporary is extended to match the lifetime of item .

Your bestItem pointer points to this temporary, but by the time you access it its lifetime has expired so the behavior is undefined.

If item is of a matching type ( const pair<const int, Item>& ), no temporary needs to be created as it can reference the map's value_type directly. bestItem is therefore a pointer into the map and everything works correctly.

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