Подтвердить что ты не робот

Обоснование для семантики вставки в std map С++?

Я немного смущен семантикой std::map::insert. Я имею в виду, я не жалуюсь - стандарт - это стандарт, и API - это так, как есть. Тем не менее,

insert будет

выполняется операция вставки для каждого вставленного элемента. другой элемент существует уже в контейнере с тем же ключом значение, если это так, элемент не вставлен, и его отображаемое значение не является изменено каким-либо образом.

И - только в версии с одним аргументом pair<iterator,bool> insert ( const value_type& x ); он даже скажет вам, даже ли он вставил (новое, возможно другое) значение в ключ (ы). Насколько я понимаю, версии итератора молча игнорируют вставки, если ключ уже существует.

Для меня это просто счетчик интуитивно понятен, я бы ожидал, что часть значения будет перезаписана, а старая часть значения будет отброшена при вставке. Очевидно, что дизайнеры STL по-разному думали - кто-нибудь знает (историческое) обоснование или может дать полное объяснение того, как чувствует существующая семантика (больше)?

Пример:

Существует несколько основных способов реализации вставки на карте с одним ключом, например std::map:

  • вставить, заменить, если уже существует
  • вставить, игнорировать, если уже существует (это поведение std:: map)
  • вставить, выбросить ошибку, если она уже существует
  • insert, UB, если он уже существует

Теперь я пытаюсь понять, почему insert_or_ignore имеет больше смысла, чем insert_or_replace (или insert_or_error)!


Я заглянул в свою копию TС++ PL (к сожалению, у меня только немецкое издание), и, что интересно, Страуступ пишет в главе 17.4.1.7 (список операций для карты): (извините грубый перевод с немецкого)

(...) Обычно, все равно, является ли ключ (sic!) новым вставлен или уже существует до вызова insert() (...)

Которое, как мне кажется, справедливо только для set, а не для карты, потому что для карты это действительно имеет значение, если добавленное значение было вставлено или старый остается на карте. (Очевидно, это не важно для ключа, так как это эквивалентно.)


Примечание. Я знаю о operator[], и я знаю о 24-м элементе Эффективный STL и предлагаемую функцию efficientAddOrUpdate. Мне просто интересно узнать о семантике insert, потому что я лично считаю, что они противоречат интуитивному.

4b9b3361

Ответ 1

Я не знаю об официальном обосновании, но я бы отметил двойственность с operator[].

Кажется очевидным, что вам понравятся два варианта вставки:

  • чисто аддитивная
  • аддитивный/деструктивный

Если мы видим a map как разреженное представление массива, то наличие operator[] имеет смысл. Я не знаю, существовали ли ранее существовавшие словари и диктовали этот синтаксис (возможно, почему бы и нет).

Кроме того, все контейнеры STL имеют несколько перегрузок insert, и это сходство интерфейсов - это то, что позволяет Generic Programming.

Следовательно, у нас есть как минимум два претендента на API: operator[] и insert.

Теперь, в С++, если вы читаете:

array[4] = 5;

естественно, что содержимое ячейки в индексе 4 было разрушено. Таким образом, естественно, что map::operator[] должен возвращать ссылку, чтобы разрешить это деструктивное обновление.

В этот момент нам также понадобится чисто аддитивная версия, и мы имеем этот метод insert. Почему бы и нет?

Конечно, можно было бы указать insert ту же семантику, что и operator[], а затем продолжить и внедрить метод insert_or_ignore сверху. Это было бы больше работы.

Поэтому, хотя я согласен, что это может быть удивительно, я думаю, что мои рассуждения не слишком ошибочны и могут быть вероятным объяснением обстоятельств, которые приводят нас сюда:)


Относительно предложенных вами альтернатив:

  • insert, UB, если он уже существует

К счастью, это не так!

  • вставить, выбросить ошибку, если она уже существует

Только Java (и производные) является исключением-сумасшедшим. С++ был задуман в то время, когда исключения были использованы для исключительных обстоятельств.

  • вставить, заменить, если уже существует
  • вставить, игнорировать, если уже существует (это поведение std:: map)

Мы согласны с тем, что выбор был между одним из них. Обратите внимание, что даже если map выбрал второй вариант, он не полностью игнорирует тот факт, что элемент уже существует, по крайней мере, в версии одного элемента, поскольку он предупреждает вас о том, что элемент не был вставлен.

Ответ 2

Метод вставки - это просто не то, что вы ищете, это звучит так... Метод вставки предназначен для того, чтобы делать то, что подразумевает название... insert values. Я согласен с тем, что возможность создания значения, если он еще не присутствует, и заменить тот, который там в противном случае, важен в некоторых ситуациях, но в других вам просто не нужно обрабатывать исключения, возвращать значения и т.д., Если вы просто хотите сделать вставку только в том случае, если это значение уже отсутствует.

Похоже, что метод, который вы ищете (как указано выше BoBTFish), вероятно, является оператором []. Просто используйте его так:

myMap["key"] = "value";

Это будет проходить через вашу карту и найти ключ "ключ" и заменить соответствующее значение "значением". Если ключ отсутствует, он создаст его. Оба метода очень полезны в разных ситуациях, и я обнаружил, что использую оба только в зависимости от того, что мне нужно.

Ответ 3

Я не утверждаю, что знаю исходное обоснование решения, но это не слишком сложно сделать. Я думаю; -)

Текущее поведение "insert или ignore" упрощает реализацию двух других - по крайней мере, для тех из нас, кто не работает над созданием и использованием функций, не являющихся членами, для дополнения стандартной функциональности библиотеки ( "it не OOP-y достаточно!" ).

Пример (записан на месте, поэтому могут присутствовать ошибки):

template<typename Map>
void insert_or_update(Map& map, typename Map::value_type const& x)
{
  std::pair<typename Map::iterator, bool> result = map.insert(x);
  if (!result.second)
    result.first->second = x.second; // or throw an exception (consider using
                                     // a different function name, though)
}

Обратите внимание, что как и есть, вышеприведенная функция на самом деле не сильно отличается от operator[] - да, она избегает инициализации по умолчанию, но в то же время (потому что я ленив) она не может воспользоваться семантикой перемещения что ваш обновленный STL, вероятно, уже поддерживает operator[].

Во всяком случае, любое другое поведение вставки для map сделало бы более утомительным для реализации остальных, поскольку map::find возвращает только конечный контрольный сигнал, если ключ еще не на карте. С помощью <algorithm> (и особенно lower_bound) было бы, конечно, еще возможно написать исполняемые вспомогательные функции без утопления их деталей реализации и уродливых общих конструкций, таких как циклы; -).

Ответ 4

От insert() вы не ожидаете, что существующие объекты в контейнере будут затронуты. Вот почему это просто не трогает их.

Ответ 5

pair<iterator,bool> < - не указывает ли часть bool, если вставка выполнена успешно или нет?

Вы можете просто обновить часть значения возвращаемого итератора, если часть bool имеет значение false, чтобы обновить существующий элемент с помощью того же ключа.