问题
对于用户定义类型的std :: unordered_map或std :: unordered_set的第三个模板参数,std :: hash有什么好的专业化,所有成员数据类型都具有良好的std :: ::散列?
对于这个问题,我将“好”定义为易于实现和理解,合理有效且不太可能产生哈希表冲突。商品的定义不包括任何有关安全性的陈述。
什么是Google可用
目前,两个StackOverflow问题是谷歌搜索“标准哈希专业化”的第一个点击。
第一个How to specialize std::hash::operator() for user-defined type in unordered containers?解决了打开std命名空间和添加模板特化是否合法的问题。
第二个How to specialize std::hash for type from other library,基本上解决了同样的问题。
这留下了当前的问题。鉴于C ++标准库的实现为标准库中的基本类型和类型定义了散列函数,为用户定义的类型专门化std :: hash的简单有效方法是什么?有没有一种很好的方法来组合标准库实现提供的哈希函数?
(编辑感谢dyp。)StackOverflow上的Another question解决了如何组合哈希函数的对。
其他Google搜索结果不再有用。
This Dobbs博士的文章指出,两个令人满意的哈希的XOR将产生一个新的令人满意的哈希值。
This文章似乎是从知识中说出来并暗示了许多事情,但却注重细节。它在第一个例子的简短评论中与Dobbs博士的文章相矛盾,称使用XOR组合散列函数会产生一个弱的结果散列函数。
因为XOR应用于任何两个相等的值导致0,我可以看出为什么XOR本身很弱。
元问题
一个很好的理由回答解释为什么这个问题无效且一般无法回答也是受欢迎的。
答案 0 :(得分:6)
一种简单的方法是使用boost::hash
库和extend it for your type。它有一个很好的扩展函数hash_combine
(std::hash
缺少),可以轻松组合结构中各个数据成员的哈希值。
换句话说:
boost::hash_value
。std::hash
并使用boost::hash_value
实现它。通过这种方式,您可以获得最好的标准和提升世界,std::hash<>
和boost::hash<>
适用于您的类型。
更好的方法是在N3980 Types Don't Know #中使用建议的新散列基础架构。此基础结构使hash_combine
不必要。
答案 1 :(得分:3)
首先,Dobbs博士的文章说两个的XOR 令人满意的哈希会产生令人满意的哈希简单 错误。这是贫穷哈希的好方法。一般来说,到 创建一个好的哈希,首先将对象分解为 子对象,每个子对象都存在良好的哈希值 结合哈希。这样做的一个简单方法就是这样 喜欢:
class HashAccumulator
{
size_t myValue;
public:
HashAccumulator() : myValue( 2166136261U ) {}
template <typename T>
HashAccumulator& operator+=( T const& nextValue )
{
myValue = 127U * myValue + std::hash<T>( nextHashValue );
}
HashAccumulator operator+( T const& nextHashValue ) const
{
HashAccumulator results( *this );
results += nextHashValue;
return results;
}
};
(此设计旨在让您使用std::accumulate
if
你有一系列的价值观。)
当然,这应该认为所有的亚型都有好处
std::hash
的实现。对于基础类型和
字符串,这是给定的;对于您自己的类型,只需应用
上面的规则是递归的,专门用std::hash
来使用
关于其子类型的HashAccumulator
。对于标准容器
一个基本类型,它有点棘手,因为你不是(正式的,
至少)允许在类型上专门化标准模板
来自标准库;你可能不得不创造
直接和明确使用HashAccumulator
的类
指定如果你需要这样一个容器的哈希值。
答案 2 :(得分:2)
在我们获得标准库以帮助解决此问题之前:
std::hash<YourType>
的定义中,创建一个SpookyHash
实例,并Init
。请注意,在流程启动或std::hash
构造中选择随机数,并将其用作初始化make it slightly harder to DoS your program, but doesn't fix the problem。operator==
(&#34;突出字段&#34;)相关的每个字段,并将其输入SpookyHash::Update
。
double
的类型:他们有char[]
的2个表示形式,可比较==
:-0.0
和0.0
。还要注意具有填充的类型。在大多数机器上,int
没有,但很难判断struct
是否会。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html#is_contiguously_hashable对此进行了讨论。SpookyHash
实例中来获得更快,更高质量的哈希值。但是,这需要为这些结构添加方法或手动提取显着字段:如果您不能这样做,只需将其std::hash<>
值提供给顶级{{}}即可接受。 1}}实例。SpookyHash
。SpookyHash::Final
的输出
醇>
答案 3 :(得分:1)
您的操作is required至
size_t
==
运营商保持一致。没有明确要求哈希值均匀分布在size_t
整数范围内。 cppreference.com
notes
[标准库]的一些实现使用将整数映射到自身的普通(标识)散列函数
避免哈希冲突加上这个弱点意味着你的类型std::hash
的特化应永远只需使用(快速)按位异或(^
)来组合数据成员的子哈希。考虑这个例子:
struct Point {
uint8_t x;
uint8_t y;
};
namespace std {
template<>
struct hash< Point > {
size_t operator()(const Point &p) const {
return hash< uint8_t >(p.x) ^ hash< uint8_t >(p.y);
}
};
}
p.x
的哈希值将在[0,255]范围内,p.y
的哈希值也是如此。因此,Point
的哈希值也将在[0,255]范围内,具有256(= 2 ^ 8)个可能值。有256 * 256(= 2 ^ 16)个唯一Point
个对象(std::size_t
通常支持2 ^ 32或2 ^ 64个值)。因此,良好散列函数的哈希冲突概率应约为2 ^( - 16)。我们的函数给出了小于2 ^( - 8)的哈希冲突的概率。这很糟糕:我们的哈希只提供8位信息,但好的哈希应提供16位信息。
如果数据成员的散列函数仅提供std::size_t
范围的低部分中的散列值,则必须在组合之前“移位”组件散列的位,因此它们各自提供独立位信息。做左移是很简单的
return (hash< uint8_t >(p.x) << 8) ^ hash< uint8_t >(p.y);
但如果hash< uint8_t >
(在这种情况下)的实现尝试在std::size_t
范围内传播哈希码值,那将丢弃信息(由于溢出)
使用乘法加法和加法的方法累计组件哈希码值,如typically done in Java,一般情况下效果更好:
namespace std {
template<>
struct hash< Point > {
size_t operator()(const Point &p) const {
const size_t prime = 257;
size_t h {hash< uint8_t >(p.x)};
h = h * prime + hash< uint8_t >(p.y);
return h;
}
};
}