Выбрать главу

Применяем контейнер std::unordered_map для пользовательских типов

 Использование контейнера std::unordered_map вместо std::map подразумевает дополнительную степень свободы при выборе типа ключа. Контейнер std::map требует наличия между ключами естественного порядка. Таким образом элементы можно отсортировать. Но если мы хотим, например, использовать в качестве ключа математический вектор? Мы не можем сказать, что вектор (0, 1) меньше или больше вектора (1, 0). Они просто указывают в разных направлениях. Это верно для контейнера std::unordered_map, поскольку мы станем различать элементы не по их величине, а по значениям их хеша. Единственное, что нужно сделать, — реализовать хеш-функцию для нашего собственного типа, а также оператор ==, который будет проверять идентичность двух объектов. В данном разделе мы рассмотрим пример такой реализации.

Как это делается

В примере мы определим простую структуру coord, которая по умолчанию не имеет хеш-функции, поэтому нам потребуется установить ее самостоятельно. Затем задействуем ее, задав соотношение значений coord с числами.

1. Сначала включим все необходимое, чтобы вывести на экран и использовать контейнер std::unordered_map:

#include <iostream>

#include <unordered_map>

2. Далее определим собственную структуру, которую не так-то просто хешировать с помощью существующих хеш-функций:

struct coord {

  int x;

  int y;

};

3. Нам нужна не только хеш-функция, которая позволит использовать структуру в качестве ключа для ассоциативного массива, основанного на хеше, но и реализация оператора сравнения:

bool operator==(const coord &l, const coord &r)

{

  return l.x == r.x && l.y == r.y;

}

4. Чтобы расширить возможности хеширования, предоставляемые STL, добавим в пространство имен std свою специализацию шаблонной структуры std::hash. Оно содержит такие же псевдонимы (задаваемые с помощью using), как и другие специализации типа hash.

namespace std

{

template <>

struct hash<coord>

{

  using argument_type = coord;

  using result_type = size_t;

5. Основная часть данной структуры — определение функции operator(). Мы просто складываем значения численных членов структуры coord. Эта техника хеширования не самая лучшая, но для демонстрации подойдет. Хорошая хеш-функция пытается максимально равномерно распределить значения в рамках допустимого диапазона, чтобы сократить количество хеш-столкновений.

  result_type operator()(const argument_type &c) const

  {

    return static_cast<result_type>(c.x)

      + static_cast<result_type>(c.y);

    }

  };

}

6. Теперь можно создать новый экземпляр контейнера std::unordered_map, который принимает в качестве ключа структуру coord и соотносит ее с некоторыми значениями. Поскольку этот раздел посвящен использованию собственных типов для контейнера std::unordered_map, наш пример почти завершен. Создадим ассоциативный массив, основанный на хеше, с нашим собственным типом, заполним его какими-нибудь элементами и выведем содержимое на экран:

int main()

{

  std::unordered_map<coord, int> m {{{0, 0}, 1}, {{0, 1}, 2},

                                    {{2, 1}, 3}};

  for (const auto & [key, value] : m) {

    std::cout << "{(" << key.x << ", " << key.y

              << "): " << value << "} ";

  }

  std::cout << '\n';

}

7. Компиляция и запуск программы дадут следующий результат:

$ ./custom_type_unordered_map

{(2, 1): 3} {(0, 1): 2} {(0, 0): 1}

Как это работает 

Обычно при создании экземпляра ассоциативного массива, основанного на хеше, например std::unordered_map, мы пишем следующую команду:

std::unordered_map<key_type, value_type> my_unordered_map;

Не вполне очевиден тот факт, что при создании компилятором контейнера уточненного типа std::unordered_map за кулисами творится настоящее волшебство. Поэтому взглянем на полное определение шаблонного типа:

template<

  class Key,

  class T,

  class Hash = std::hash<Key>,

  class KeyEqual = std::equal_to<Key>,

  class Allocator = std::allocator< std::pair<const Key, T>>