← Назад к вопросам
Как происходит сравнение в std::unordered_map, если ключ является объектом?
2.2 Middle🔥 111 комментариев
#STL контейнеры и алгоритмы#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Сравнение в std::unordered_map с объектами как ключи
Для использования объектов в качестве ключей в std::unordered_map нужно определить две вещи: хэш-функцию и функцию сравнения (operator==).
Проблема: объект как ключ
struct Point {
int x, y;
};
int main() {
std::unordered_map<Point, std::string> map;
// Ошибка! Нет std::hash<Point> и operator== для Point
}
Решение 1: Специализация std::hash
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// Специализируем std::hash для Point
namespace std {
template<>
struct hash<Point> {
size_t operator()(const Point& p) const {
// Комбинируем хэши x и y
size_t h1 = std::hash<int>{}(p.x);
size_t h2 = std::hash<int>{}(p.y);
// XOR (простой, но не идеальный способ)
return h1 ^ (h2 << 1);
// Или boost::hash_combine
// h1 ^= h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2);
// return h1;
}
};
}
int main() {
std::unordered_map<Point, std::string> map;
Point p1{1, 2};
Point p2{1, 2};
Point p3{2, 3};
map[p1] = "First";
map[p2] = "Second"; // Перезаписывает first (p1 == p2)
map[p3] = "Third";
std::cout << map.size() << std::endl; // 2 (p1 и p2 — один ключ)
}
Решение 2: Через пользовательскую функцию сравнения
struct Point {
int x, y;
};
struct PointHash {
size_t operator()(const Point& p) const {
return std::hash<int>{}(p.x) ^ (std::hash<int>{}(p.y) << 1);
}
};
struct PointEqual {
bool operator()(const Point& a, const Point& b) const {
return a.x == b.x && a.y == b.y;
}
};
int main() {
std::unordered_map<Point, std::string, PointHash, PointEqual> map;
map[{1, 2}] = "Point"; // Работает
std::cout << map[{1, 2}] << std::endl; // "Point"
}
Процесс поиска в unordered_map
1. Вычисление хэша ключа
size_t hash = hash_function(key);
2. Определение bucket'а
size_t bucket_index = hash % bucket_count();
3. Перебор элементов в bucket'е
for (auto& pair : buckets[bucket_index]) {
if (equality_function(pair.first, key)) {
return pair.second;
}
}
Практический пример: String как ключ
struct Person {
std::string name;
int age;
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
namespace std {
template<>
struct hash<Person> {
size_t operator()(const Person& p) const {
// Комбинируем хэши string и int
size_t h1 = std::hash<std::string>{}(p.name);
size_t h2 = std::hash<int>{}(p.age);
// boost::hash_combine эквивалент
h1 ^= h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2);
return h1;
}
};
}
int main() {
std::unordered_map<Person, std::string> map;
Person alice{"Alice", 30};
Person bob{"Bob", 25};
map[alice] = "Engineer";
map[bob] = "Designer";
std::cout << map[{"Alice", 30}] << std::endl; // "Engineer"
return 0;
}
Как работает unordered_map внутри
template<typename Key, typename Value, typename Hash, typename Equal>
class unordered_map {
private:
std::vector<std::list<std::pair<const Key, Value>>> buckets;
Hash hash_func;
Equal equal_func;
size_t size = 0;
public:
Value& operator[](const Key& key) {
// 1. Вычислить хэш
size_t h = hash_func(key);
// 2. Найти bucket
size_t idx = h % buckets.size();
// 3. Найти в bucket'е
for (auto& pair : buckets[idx]) {
if (equal_func(pair.first, key)) {
return pair.second; // Найден
}
}
// 4. Добавить новый элемент
buckets[idx].emplace_back(key, Value{});
size++;
// 5. Может быть rehash
if (load_factor() > max_load_factor()) {
rehash(buckets.size() * 2);
}
return buckets[idx].back().second;
}
void rehash(size_t new_size) {
std::vector<std::list<...>> new_buckets(new_size);
// Переместить все элементы в новые bucket'ы
for (auto& bucket : buckets) {
for (auto& pair : bucket) {
size_t new_h = hash_func(pair.first) % new_size;
new_buckets[new_h].splice(/*move pair*/);
}
}
buckets = std::move(new_buckets);
}
};
Важность хорошей хэш-функции
Плохая хэш-функция:
// Всегда возвращает одно значение
struct BadHash {
size_t operator()(const Point& p) const {
return 42; // Все элементы в одном bucket'е!
}
};
// Результат: O(n) вместо O(1) в среднем
Хорошая хэш-функция:
struct GoodHash {
size_t operator()(const Point& p) const {
// Распределяет элементы равномерно
size_t h = 0;
h ^= std::hash<int>{}(p.x) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<int>{}(p.y) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
Lambda как hash function (C++17+)
struct Point { int x, y; };
int main() {
auto hash = [](const Point& p) -> size_t {
return std::hash<int>{}(p.x) ^ (std::hash<int>{}(p.y) << 1);
};
auto equal = [](const Point& a, const Point& b) {
return a.x == b.x && a.y == b.y;
};
std::unordered_map<Point, std::string,
decltype(hash),
decltype(equal)> map(0, hash, equal);
map[{1, 2}] = "Point";
}
Для tuple как ключ (C++17)
template<typename... Args>
struct TupleHash {
size_t operator()(const std::tuple<Args...>& t) const {
return hash_tuple(t, std::index_sequence_for<Args...>{});
}
private:
template<typename Tuple, std::size_t... I>
size_t hash_tuple(const Tuple& t, std::index_sequence<I...>) const {
size_t seed = 0;
(..., (seed ^= std::hash<std::tuple_element_t<I, Tuple>>{}(std::get<I>(t)) +
0x9e3779b9 + (seed << 6) + (seed >> 2)));
return seed;
}
};
int main() {
using Key = std::tuple<int, std::string>;
std::unordered_map<Key, int, TupleHash<int, std::string>> map;
map[{1, "hello"}] = 100;
}
Рекомендации
1. Для простых типов — используй встроенный std::hash
std::unordered_map<std::string, int> map; // std::hash<string> встроен
2. Для своих типов — специализируй std::hash
namespace std {
template<> struct hash<MyClass> { /* ... */ };
}
3. Проверяй performance — может быть нужен custom hash
// Если load_factor() > 0.8 — хэш функция плохая
std::cout << map.load_factor() << std::endl;
Итог
Для использования объектов в std::unordered_map:
- Определить operator== для равенства
- Определить std::hash или custom hash function
- Убедиться, что хэш функция распределяет элементы равномерно
- Помнить: правильная хэш-функция = O(1) производительность
Знание этого важно для проектирования правильных data structures в production коде.