【问题标题】:Values end up in same bucket with user-provided specialization of hash using std::unordered_set值以用户提供的使用 std::unordered_set 的散列特化结束在同一个桶中
【发布时间】:2021-06-01 13:13:41
【问题描述】:

我有这段代码,我在其中为我的类型X 定义了一个自定义哈希,以便能够将它存储在std::unordered_set 中。为此,我使用如下所示的参数地址。

我的第一个问题:

为什么所有X'es都在同一个桶中?

来自26.2.7 Unordered associative containers 我知道: "Keys with the same hash code appear in the same bucket." 我也知道更好的方法是将operator() 定义为:

auto operator()(X const &x) const { return hash<int>{}(x.m_i); },

但我想了解这里发生了什么。它与X{i} 是一个临时的(prvalue)有关 - 所有X{i} 的地址都相同吗?这是定义的还是我在UB-land?

我的第二个问题:

当我将noexcept 添加到operator() 时发生了什么变化/发生了什么?

#include <iostream>
#include <ostream>
#include <unordered_set>

class X
{
  int m_i;

public:
  explicit X(int i) : m_i{i} {}
  auto operator==(X const &x) const { return m_i == x.m_i; }
};

template <>
struct std::hash<X> {
  auto operator()(X const &x) const { return hash<X const *>{}(&x); }
  // what's the difference to above version?
  // auto operator()(X const &x) const noexcept { return hash<X const *>()(&x); }
  // better:
  // auto operator()(X const &x) const { return hash<int>{}(x.m_i); } // better
};

int main()
{
  std::unordered_set<X> usx;

  for (int i{}; i < 10; ++i)
    usx.insert(X{i});
  for (std::size_t index{}; index < usx.bucket_count(); ++index)
    std::cout << "bucket[" << index << "]: " << usx.bucket_size(index) << '\n';
}

没有noexcept 的输出(g++ 9.3):

bucket[0]: 10
bucket[1]: 0
...
bucket[12]: 0

noexcept (g++ 9.3) 的输出:

bucket[0]: 2
bucket[1]: 0
...
bucket[12]: 0

输出也因不同的编译器而异 - 使用 noexcept - (clang 12.0.0) 例如给出:

bucket[0]: 0
bucket[1]: 0
bucket[2]: 1
...
bucket[12]: 0

【问题讨论】:

  • 不是比较相等的对象也需要产生相同的散列吗?编辑:std::hash 要求 #4:对于两个相等的参数 k1k2std::hash&lt;Key&gt;()(k1) == std::hash&lt;Key&gt;()(k2)
  • 如果哈希是基于地址的,并且复制后哈希不会改变,则需要确保对象不可复制或不可分配。然后,如果这样的约束是一个问题,你会看到没有这些操作的后果。
  • @François Andrieux 这意味着,使用地址作为哈希的一部分无论如何都不是一个好主意,对吧?
  • @SebastianWilke 为了将它与未排序的容器一起使用,它永远不会起作用。总的来说,它不太可能有用。散列应该代表对象的,而对象的地址基本上绝不是其值的一部分。如果是,则该对象也是不可移动和不可复制的,并且不会与容器一起使用。

标签: c++ hash bucket unordered-set


【解决方案1】:

在我看来,您使用结构的地址作为哈希机制而不是结构本身。例如,在以下行中:

auto operator()(X const &x) const { return hash<X const *>{}(&x); }

您将获得一个指针您新创建的x 并基于此返回哈希值。现在,您将它们全部放在同一个存储桶中的原因是,当您在for 循环中初始化变量时,编译器使用相同的内存位置。这意味着&amp;x 的结果在循环中将始终具有相同的值。

您可能会做两件事来获得不同的结果。我的建议是将上述行替换为:

auto operator()(X const &x) const { return hash<int>{}(x.m_i); }

但是,你也可以可能侥幸逃脱:

X x1(4);
X x2(5);

usx.insert(x1);
usx.insert(x2);

因为这可能会有x1x2 在不同的地址。不过,这很有可能被打破,因为如果你给编译器一个高优化级别,内存位置可能最终会相同。

【讨论】:

    猜你喜欢
    • 2021-01-08
    • 2016-07-21
    • 1970-01-01
    • 2016-02-21
    • 1970-01-01
    • 2012-01-09
    • 1970-01-01
    • 2015-10-01
    • 2015-10-22
    相关资源
    最近更新 更多