当我们需要在C++中使用自定义类型作为unordered_map的键时,常常会遇到编译错误。这是因为unordered_map底层使用哈希表实现,它需要知道如何计算键的哈希值以及如何比较键是否相等。本文将深入探讨如何为自定义类型实现这些必要组件。
unordered_map要求每个键类型必须满足两个基本条件:
对于内置类型如int、string等,标准库已经提供了默认的哈希函数和相等比较。但当使用自定义结构体或类时,我们需要自己实现这些功能。
cpp复制struct Student {
std::string name;
int id;
};
// 尝试直接使用会导致编译错误
std::unordered_map<Student, int> student_scores; // 错误!
编译器会提示类似这样的错误:"error: static assertion failed: hash function must be invocable with an argument of key type"
我们需要创建一个函数对象(functor)来为Student类型计算哈希值:
cpp复制struct StudentHash {
std::size_t operator()(const Student& s) const {
std::size_t h1 = std::hash<std::string>{}(s.name);
std::size_t h2 = std::hash<int>{}(s.id);
return h1 ^ (h2 << 1); // 组合哈希值
}
};
上述简单异或方法可能导致哈希冲突较多。更专业的做法是使用boost的hash_combine技术:
cpp复制#include <functional> // for std::hash
template <class T>
inline void hash_combine(std::size_t& seed, const T& v) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
struct StudentHash {
std::size_t operator()(const Student& s) const {
std::size_t seed = 0;
hash_combine(seed, s.name);
hash_combine(seed, s.id);
return seed;
}
};
这种方法能产生更好的哈希分布,减少冲突。
最简单的方式是为自定义类型重载==运算符:
cpp复制struct Student {
std::string name;
int id;
bool operator==(const Student& other) const {
return name == other.name && id == other.id;
}
};
如果不希望或不能修改Student类,可以单独实现一个equal_to谓词:
cpp复制struct StudentEqual {
bool operator()(const Student& lhs, const Student& rhs) const {
return lhs.name == rhs.name && lhs.id == rhs.id;
}
};
将哈希函数和相等比较组合起来使用:
cpp复制#include <iostream>
#include <unordered_map>
#include <string>
struct Student {
std::string name;
int id;
bool operator==(const Student& other) const {
return name == other.name && id == other.id;
}
};
struct StudentHash {
std::size_t operator()(const Student& s) const {
std::size_t h1 = std::hash<std::string>{}(s.name);
std::size_t h2 = std::hash<int>{}(s.id);
return h1 ^ (h2 << 1);
}
};
int main() {
std::unordered_map<Student, int, StudentHash> student_scores;
Student s1{"Alice", 1001};
Student s2{"Bob", 1002};
student_scores[s1] = 95;
student_scores[s2] = 88;
std::cout << "Alice's score: " << student_scores[s1] << std::endl;
std::cout << "Bob's score: " << student_scores[s2] << std::endl;
return 0;
}
如果使用了独立的equal_to谓词,声明方式为:
cpp复制std::unordered_map<Student, int, StudentHash, StudentEqual> student_scores;
当哈希函数设计不当时,可能导致大量冲突,影响性能。解决方案:
cpp复制std::unordered_map<Student, int, StudentHash> map;
map.max_load_factor(0.7); // 设置最大负载因子
map.rehash(100); // 预分配桶数量
对于复杂类型的哈希计算,可以考虑缓存哈希值:
cpp复制struct Student {
std::string name;
int id;
mutable std::size_t cached_hash = 0;
bool operator==(const Student& other) const {
return name == other.name && id == other.id;
}
std::size_t hash() const {
if (cached_hash == 0) {
std::size_t h1 = std::hash<std::string>{}(name);
std::size_t h2 = std::hash<int>{}(id);
cached_hash = h1 ^ (h2 << 1);
}
return cached_hash;
}
};
struct StudentHash {
std::size_t operator()(const Student& s) const {
return s.hash();
}
};
如果需要为多种类型实现哈希,可以使用模板:
cpp复制template <typename T>
struct GenericHash {
std::size_t operator()(const T& val) const {
return std::hash<T>{}(val);
}
};
// 特化版本
template <>
struct GenericHash<Student> {
std::size_t operator()(const Student& s) const {
std::size_t h1 = std::hash<std::string>{}(s.name);
std::size_t h2 = std::hash<int>{}(s.id);
return h1 ^ (h2 << 1);
}
};
考虑一个图形处理应用,我们需要将二维点映射到颜色值:
cpp复制struct Point {
int x;
int y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
struct PointHash {
std::size_t operator()(const Point& p) const {
std::size_t h1 = std::hash<int>{}(p.x);
std::size_t h2 = std::hash<int>{}(p.y);
return h1 ^ (h2 << 1);
}
};
int main() {
std::unordered_map<Point, std::string, PointHash> point_colors;
point_colors[{1, 2}] = "red";
point_colors[{3, 4}] = "blue";
std::cout << "Color at (1,2): " << point_colors[{1, 2}] << std::endl;
return 0;
}
C++11以后,我们可以使用Lambda表达式来定义哈希和比较函数:
cpp复制auto hash = [](const Student& s) {
return std::hash<std::string>{}(s.name) ^ std::hash<int>{}(s.id);
};
auto equal = [](const Student& lhs, const Student& rhs) {
return lhs.name == rhs.name && lhs.id == rhs.id;
};
std::unordered_map<Student, int, decltype(hash), decltype(equal)>
student_scores(10, hash, equal);
注意:使用Lambda时需要指定初始桶数量和函数对象。