boost::hash_combine里的魔法

最近又和朋友讨论到C++里的 hash_combine , 我对它的疑问有两个:

  1. 为什么它至今都没被收录到C++标准库里?
  2. boost::hash_combine 源代码里那些魔法数字是怎么回事?

这篇文章再来钻研一下.

为什么hash_combine不在标准库里?

我感觉要支持对任何class做hash, hash_combine 是必不可少的, 它可以让我们把基础类型的hash都合并起来, 比如一个class包含一个 string 和一个 int 成员, 这个class的hash就是他们俩成员hash的合并.

但是我们现在还是只能用 boost 而不是标准库 std hash_combine , 再次挖了一下原因, 如下:

2012年和2014年分别有两个提案, 但2014年的提案被拒了因为他们觉得2012年的方案更好, 但是直到C++17依然没有发布任何相关的代码.

归结于原因就是C++委员会过于追求完美, 至今没有发布一个可用的解决方案.

参考: open-std.org/jtc1/sc22/


boost::hash_combine里的魔法数字又是什么?

那么现在只能用 boost::hash_combine 了, 在没有 boost 的情况下只能照抄一份留在自己库里用, 看看 boost 的源代码:

参考: boost.org/doc/libs/1_64

template <typename SizeT>
inline void hash_combine_impl(SizeT& seed, SizeT value)
    seed ^= hasher(value) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

按执行顺序我们一个一个看:

hasher(value) : 就是对我们需要的数的哈希的结果.

我们期望它是一个比较散比较随机的哈希数, 后续其它操作都是对可能不那么随机的哈系数的补充操作.

seed : 可以自选一个初始种子数, 或是前一个hash的结果.

0x9e3779b9 : 这个魔法数字后面细说.

(seed<<6) + (seed>>2) : 这个就是单纯的比特位的洗牌, 加上左挪6位和右挪2位的结果后, 能打散避免在比特位上聚集的数. 比如两个连续的数, 比特位上就差一位, 洗牌后会相差更多.

^= : XOR根据种子和哈希计算后的结果再次翻转他们俩的比特位, 再次打乱数字.

参考: stackoverflow.com/quest

魔法数字: 0x9e3779b9

剩下这个魔法数字是什么, 有什么用呢?

首先它是一个32位随机比特组成的数字. 随机的意思是每一个比特位上出现0和1的概率都应该相等, 或者每个比特之间都没有相关性. 通常我们可以用一个无理数的二进制展开来获得这种数字.

我们用上这么一个随机数的原因是避免种子数比特位上刚好都是0, 这样不管怎么做 hash_combine 结果都是0.

在我们这个案例里, 就是用上了黄金比例这么个无理数:

phi = (1 + sqrt(5)) / 2
2^32 / phi = 0x9e3779b9

哈希里用到黄金比例这个数的最早来源是这里:

burtleburtle.net/bob/ha

ub4 hash( k, length, initval)
register ub1 *k;        /* the key */
register ub4  length;   /* the length of the key */
register ub4  initval;  /* the previous hash, or an arbitrary value */
   register ub4 a,b,c,len;