必须自己写 std::hash 特化:当自定义类用作 std::unordered_set 或 std::unordered_map 的键且非标准类型时,否则编译报错;需在 std 命名空间内全特化,返回 std::size_t,纯函数,满足 a == b ⇒ hash(a) == hash(b)。

什么时候必须自己写 std::hash 特化?
当你把自定义 C++ 类用作 std::unordered_set 或 std::unordered_map 的键时,编译器会尝试调用 std::hash。如果没特化,就会报错:error: call to implicitly-deleted default constructor of 'std::hash。这不是“建议”而是硬性要求——只要用了无序容器且类型非标准内置/字符串/指针,就得提供哈希逻辑。
如何安全地特化 std::hash?
必须在 std 命名空间内为你的类型全特化 std::hash,且不能改变模板参数数量(即只能是 template struct hash)。常见错误是漏掉 namespace std 或写成偏特化(C++ 不允许对 std::hash 做偏特化)。
关键点:
- 特化必须定义在全局或你自己的头文件中,且在首次使用前可见(通常放头文件末尾或单独
hash.h) - 返回类型必须是
std::size_t - 不能抛异常、不能有副作用、必须是纯函数(相同输入恒定输出)
- 强烈建议复用标准组件:用
std::hash对成员逐个哈希,再用std::hash_combine风格混合(C++17 起标准未提供,需手写)
namespace std {
template<>
struct hash {
size_t operator()(const MyPoint& p) const noexcept {
size_t h1 = hash{}(p.x);
size_t h2 = hash{}(p.y);
// 简单但可用的 combine:避免位移为 0
return h1 ^ (h2 << 1);
}
};}
为什么不能直接用 std::hash{}(x) ^ std::hash{}(y) ?
异或(^)本身不抗碰撞:(1,2) 和 (2,1) 会得到相同哈希值。更糟的是,若多个字段同值(如 (0,0), (1,1)),哈希全撞成 0。实际应引入位移、乘法或标准库推荐的 std::hash
立即学习“C++免费学习笔记(深入)”;
更鲁棒的做法(兼容 C++11+):
inline size_t hash_combine(size_t lhs, size_t rhs) noexcept {
return lhs ^ (rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2));
}
// 在 operator() 中:
return hash_combine(hash{}(p.x), hash{}(p.y));
注意:不要用 std::hash 对 std::string 成员直接取 .c_str() 哈希——那哈希的是指针地址,不是内容。
结构体含指针、浮点数或自定义比较逻辑时怎么办?
含裸指针(如 int*)作为键成员极危险:不同对象可能指向相同地址,或同一对象多次插入时指针值变化导致哈希不一致。应避免;若必须,确保指针语义等价于值(如用 std::shared_ptr 并哈希其 get() 地址)。
浮点数要小心 NaN:所有 NaN 的哈希值必须相同(std::hash 已处理),但手动比较时仍需显式检查 std::isnan 再决定是否参与哈希。
若类已有 operator==,哈希函数必须满足:若 a == b,则 hash(a) == hash(b)。违反这点会导致 unordered_map 查不到已存在的键——这是最隐蔽也最难调试的问题之一。











