{
auto a (race_placement.extract(3));
auto b (race_placement.extract(8));
5. Теперь поменяем местами ключи Боузера и Донки Конга — младшего. Несмотря на то, что ключи элементов ассоциативного массива обычно неизменяемы (поскольку объявлены с модификатором
const
), можно изменить ключи извлеченных элементов, полученных с помощью метода extract
.
swap(a.key(), b.key());
6. Метод insert контейнера
std::map
в С++17 получил новую перегруженную версию, которая принимает дескрипторы извлеченных узлов, чтобы вставить их в контейнер, не вызывая выделения памяти:
race_placement.insert(move(a));
race_placement.insert(move(b));
}
7. Выходим из области видимости, и на этом все. Выводим на экран новые места гонщиков и позволяем приложению завершиться:
print(race_placement);
}
8. Компиляция и запуск программы дадут следующий результат. Сначала мы видим изначальную расстановку гонщиков, а затем новую, которую получили после изменения позиций Боузера и Донки Конга — младшего:
$ ./mapnode_key_modification Race placement:
1: Mario
2: Luigi
3: Bowser
4: Peach
5: Yoshi
6: Koopa
7: Toad
8: Donkey Kong Jr.
Race placement:
1: Mario
2: Luigi
3: Donkey Kong Jr.
4: Peach
5: Yoshi
6: Koopa
7: Toad
8: Bowser
Как это работает
В C++17 контейнер
std::map
получил новую функцию-член extract
. Она поставляется в двух версиях:
node_type extract(const_iterator position);
node_type extract(const key_type& x);
В этом примере мы используем вторую версию, которая принимает ключ, а затем находит и извлекает соответствующий ему элемент ассоциативного массива. Первая версия функции принимает итератор, что подразумевает ее более быструю работу, поскольку ей не нужно искать элемент.
Если мы попробуем извлечь с помощью второго метода (выполняющего поиск по ключу) элемент, которого не существует, то получим пустой экземпляр типа
node_type
. Метод-член empty()
возвращает булево значение, которое указывает, пуст ли экземпляр node_type
. Вызов любого метода для пустого экземпляра приводит к неопределенному поведению.После извлечения узлов можно изменить их ключи с помощью метода
key()
, который предоставляет неконстантный доступ к ключам, несмотря на то что они обычно являются неизменяемыми.Обратите внимание: для повторного добавления узлов в ассоциативный массив нужно передать их в функцию
insert
. Это логично, поскольку функция extract
стремится избежать создания ненужных копий и выделения памяти. Еще одно примечание: хотя мы перемещаем экземпляр типа node_type
, на самом деле никакие значения из контейнера не перемещаются.
Дополнительная информация
Элементы ассоциативного массива, которые были извлечены с помощью метода
extract
, обычно довольно гибкие. Можно извлечь элементы из одного экземпляра типа map
и вставить их в любой другой экземпляр типа map
или даже multimap
. Это верно и для контейнеров unordered_map
и unordered_multimap
, а также set/multiset
и, соответственно, unordered_set/unordered_multiset
.Для того чтобы можно было перемещать элементы из одной структуры ассоциативного массива или множества в другую, типы ключей, значений и средство выделения памяти должны быть идентичны. Обратите внимание: даже при совпадении этих типов мы не можем перемещать элементы из map в
unordered_map
или из set
в unordered_set
. Применяем контейнер std::unordered_map для пользовательских типов
Использование контейнера
std::unordered_map
вместо std::map
подразумевает дополнительную степень свободы при выборе типа ключа. Контейнер std::map
требует наличия между ключами естественного порядка. Таким образом элементы можно отсортировать. Но если мы хотим, например, использовать в качестве ключа математический вектор? Мы не можем сказать, что вектор (0, 1) меньше или больше вектора (1, 0). Они просто указывают в разных направлениях. Это верно для контейнера std::unordered_map
, поскольку мы станем различать элементы не по их величине, а по значениям их хеша. Единственное, что нужно сделать, — реализовать хеш-функцию для нашего собственного типа, а также оператор ==
, который будет проверять идентичность двух объектов. В данном разделе мы рассмотрим пример такой реализации.
Как это делается
В примере мы определим простую структуру
coord
, которая по умолчанию не имеет хеш-функции, поэтому нам потребуется установить ее самостоятельно. Затем задействуем ее, задав соотношение значений coord
с числами.
1. Сначала включим все необходимое, чтобы вывести на экран и использовать контейнер
std::unordered_map
:
#include
#include
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
{
using argument_type = coord;
using result_type = size_t;
5. Основная часть данной структуры — определение функции
operator()
. Мы просто складываем значения численных членов структуры coord
. Эта техника хеширования не самая лучшая, но для демонстрации подойдет. Хорошая хеш-функция пытается максимально равномерно распределить значения в рамках допустимого диапазона, чтобы сократить количество хеш-столкновений.
result_type operator()(const argument_type &c) const
{
return static_cast(c.x)
+ static_cast(c.y);
}
};
}
6. Теперь можно создать новый экземпляр контейнера
std::unordered_map
, который принимает в качестве ключа структуру coord
и соотносит ее с некоторыми значениями. Поскольку этот раздел посвящен использованию собственных типов для контейнера std::unordered_map
, наш пример почти завершен. Создадим ассоциативный массив, основанный на хеше, с нашим собственным типом, заполним его какими-нибудь элементами и выведем содержимое на экран:
int main()
{
std::unordered_map m {{{0, 0}, 1}, {{0, 1}, 2},
{{2, 1}, 3}};
for (const auto & [key, value] : m) {
std::cout << "{(" << key.x << ", " << key.y
<