问题
I'm curious about this behaviour. I found that assigning an unordered_map
changes the internal order of the unordered map, without any insertion/deletion:
unordered_map<int, string> m1;
unordered_map<int, string> m2;
unordered_map<int, string> m3;
m1[2] = "john";
m1[4] = "sarah";
m1[1] = "mark";
m2 = m1;
m3 = m2;
for(auto it = m1.begin(); it != m1.end(); ++it) {
cout << it->second << " ";
}
cout << endl;
for(auto it = m2.begin(); it != m2.end(); ++it) {
cout << it->second << " ";
}
cout << endl;
for(auto it = m3.begin(); it != m3.end(); ++it) {
cout << it->second << " ";
}
cout << endl;
outputs:
mark sarah john
john sarah mark
mark sarah john
I know that there isn't any specific order maintained on an unordered_map
due to the fact that internally is a hash table, so an element insertion can end anywhere and a re-hash will mix it all.
However, here the order is changing just after an assignment. I expected the order to be the same, as I thought it would just copy the underlying storage.
The first explanation I thought was that maybe unordered_map
is taking advantage of the copy to re-hash the new map into a more optimal arrangement. However, I tried to repeat an assignment on a new map (m3) from m2 and the order of m2 is not preserved in m3.
Why does assigning the map change the order?
My compiler is Apple LLVM version 8.1.0 (clang-802.0.42)
回答1:
This is an implementation detail of libc++:
_LIBCPP_INLINE_VISIBILITY unordered_map& operator=(const unordered_map& __u) { #ifndef _LIBCPP_CXX03_LANG __table_ = __u.__table_; #else if (this != &__u) { __table_.clear(); __table_.hash_function() = __u.__table_.hash_function(); __table_.key_eq() = __u.__table_.key_eq(); __table_.max_load_factor() = __u.__table_.max_load_factor(); __table_.__copy_assign_alloc(__u.__table_); insert(__u.begin(), __u.end()); } #endif return *this; }
From libc++'s unordered_map header
If we assume you are using C++11 or greater, then this basically works by clearing the old hashtable, then inserting the elements of __u
into this vector.
That means that when you do:
m2 = m1;
It's roughly equivalent to the following code:
m2.clear();
m2.max_load_factor(m1.max_load_factor());
m2.insert(m1.begin(), m1.end());
This doesn't happen when you use libstdc++, as its implementation of operator=
is just = default
(see libstdc++'s unordered_map header)
回答2:
Since obviously this is implementation-specific (it is an unordered map after all) I'm going to make an educated speculation.
If mark
and john
have the same hash and collide for the number of buckets in question, and the implementation uses chaining we may be able to explain this. If the chaining implementation inserts new items at the front (constant time even for a single-linked list) then every time you assign the container the chained-item order will be swapped.
来源:https://stackoverflow.com/questions/45620007/order-of-unordered-map-changes-on-assignment