Tag Archives: map

Заметка о map::find

Рассмотрим следующий код (C++17):

#include <map>
#include <string>
#include <string_view>

int main()
{
  using namespace std;
  map<string, int> m;
  string_view key = "none";
  return m.find(key) != m.end();
}

Этот код не компилируется. Первая реакция: как же так? Ведь в C++14 добавили версию find, принимающую ключ произвольного типа, лишь бы значение этого типа можно было сравнивать на меньше с key_type нашего map?

Ключевое слово здесь «сравнивать». Контейнер map делает это с помощью объекта типа key_compare, который передаётся третьим (необязательным) параметром шаблона. Этот тип по умолчанию есть less<key_type> (в примере — less<string>). Так как его в примере не видно, трудно сразу догадаться о причине данной проблемы. Как нетрудно видеть, эта версия принимает только key_type, а в нашем случае string_view не приводится неявно к string. (Кстати! если вы обращаетесь к такому map, передавая в качестве ключа си-строку, например, константу "…", то она будет неявно сконвертирована в string, причём, возможно неоднократно, что будет влечь ненужный и скрытый расход вычислительных ресурсов.)

Чтобы это исправить, рекомендуется заменить версию less по умолчанию, на универсальную версию less (доступную также начиная с C++14). Следующий код компилируется и работает так, как предполагалось (кроме того, он не влечёт того скрытого расхода вычислительных ресурсов, поскольку теперь ключ не конвертируется в значение key_type):

#include <functional>
#include <map>
#include <string>
#include <string_view>

int main()
{
  using namespace std;
  map<string, int, less<>> m;
  string_view key = "none";
  return m.find(key) != m.end();
}

Оператор [] для multimap

Наверное, не только у меня бывали моменты, когда отсутствие в std::multimap возможности оперировать блоками значений, отвечающих одному ключу, хотя бы в духе совместимости с std::map раздражало и казалось странным. В C++11 добавили хэш-таблицы, включая std::unordered_multimap — аналог std::multimap (сбалансированного дерева двоичного поиска), но расширение интерфейса в сторону работы с диапазонами не последовало. К сожалению, пример boost.range и языка D (см. также ссылки здесь) пока не находят отклика в Стандартной библиотеке C++.

Несколько месяцев назад я написал расширение, добавляющее оператор [] и аналогичную функцию at() в произвольный класс, совместимый с std::multimap или std::unordered_multimap. Подмешивание функционала происходит с помощью наследования от базового контейнера, переданного параметром шаблона. Традиционно принято считать, что наследовать от стандартных контейнеров — плохо, так как они изначально для этого не предназначены (невиртуальные деструкторы). В данном случае этим стереотипом можно пренебречь, так как класс-наследник не добавляет никакого состояния к объекту базового класса и никак не влияет на его жизненный цикл. Добавляемая функциональность могла бы быть реализована внешними методами, если бы они были в C++ (есть в D).

Оператор [] возвращает прокси-объект «диапазон» MapRangeView, для которого определён ряд операций (например, присваивание, begin/end), позволяющие оперировать группой элементов multimap как контейнером с минимальным функционалом (например, обходить их с помощью for(:)). Впрочем, данный класс предоставляет также ряд высокоуровневых функций (синтаксический сахар). Но есть и свои мелкие неприятности:

map1["key1"] = map2["key2"]; // копирование прокси-объекта
// содержимое map1 и map2 не поменялось

Я решил не нагружать базовый оператор присваивания подменой содержимого контейнера. Вместо этого добавлен ещё один очень простой прокси-класс BasicRange, в который оборачивается наш диапазон, если требуется скопировать или переместить его элементы (для перемещения используется std::move_iterator).

map1["key1"] = map2["key2"].copying(); // копирование элементов
map1["key3"] = map2["key4"].moving();  // перемещение элементов
map2["key4"].erase(); // удаление того, что осталось после перемещения

У получившегося кода есть свои недостатки, но, что более важно, я пока не пользовался им всерьёз и не могу сказать, что уверен в отсутствии опасных ошибок. Исходники: определение классов диапазонов, контейнер-обёртка multimap, простенькие тесты.