unordered_map 的顺序更改分配

Order of unordered_map changes on assignment

我很好奇这种行为。我发现分配一个 unordered_map 改变了无序映射的内部顺序,没有任何 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;

输出:

mark sarah john 
john sarah mark 
mark sarah john

我知道 unordered_map 上没有维护任何特定顺序,因为内部是一个哈希 table,因此元素插入可以在任何地方结束并重新哈希会混合所有。

但是,此处的顺序在分配后立即发生变化。我希望顺序相同,因为我认为它只会复制底层存储。

我想到的第一个解释可能是 unordered_map 正在利用副本将新地图重新散列为更优化的排列。但是,我尝试在 m2 的新地图 (m3) 上重复赋值,但 m3 中未保留 m2 的顺序。

为什么分配地图会改变顺序?

我的编译器是 Apple LLVM 版本 8.1.0 (clang-802.0.42)

因为显然这是特定于实现的(毕竟它是一个 无序 映射)我将进行有根据的推测。

如果 markjohn 具有相同的散列并在有问题的桶数上发生冲突,并且实现使用链接,我们也许可以解释这一点。如果链式实现在前面插入新项目(即使对于单链表也是恒定时间),那么每次您分配容器时,链式项目的顺序都会被交换。

这是 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

如果我们假设您使用的是 C++11 或更高版本,那么这基本上可以通过清除旧哈希表,然后将 __u 的元素插入此向量中来实现。

这意味着当你这样做时:

m2 = m1;

大致相当于下面的代码:

m2.clear();
m2.max_load_factor(m1.max_load_factor());
m2.insert(m1.begin(), m1.end());

当您使用 libstdc++, as its implementation of operator= is just = default (see libstdc++'s unordered_map header)

时不会发生这种情况