Почему оператор std :: map [] создает объект, если ключ не существует? - PullRequest
31 голосов
/ 28 октября 2009

Я почти уверен, что где-то уже видел этот вопрос (comp.lang.c ++? Google, похоже, и там его не находит), но быстрый поиск здесь, похоже, не находит его, поэтому вот он:

Почему оператор std :: map [] создает объект, если ключ не существует? Я не знаю, но для меня это кажется нелогичным, если вы сравните с большинством других операторов [] (например, std :: vector), где, если вы используете его, вы должны быть уверены, что индекс существует. Мне интересно, каково обоснование для реализации этого поведения в std :: map. Как я уже сказал, не будет ли более интуитивно понятным вести себя как индекс в векторе и вылетать (я думаю, что поведение не определено) при обращении с недействительным ключом?

Уточнение моего вопроса после просмотра ответов:

Хорошо, до сих пор я получил много ответов о том, что в основном это дешево, так почему бы и нет, или что-то подобное. Я полностью согласен с этим, но почему бы не использовать специальную функцию для этого (я думаю, что один из комментариев сказал, что в Java нет оператора [] и функция называется put)? Я хочу сказать, почему оператор map [] не работает как вектор? Если я использую operator [] для индекса вне диапазона для вектора, мне не хотелось бы, чтобы он вставлял элемент , даже если он был дешевым , потому что это, вероятно, означало ошибку в моем коде. Я хочу сказать, почему это не то же самое с картой. Я имею в виду, что для меня использование оператора [] на карте означало бы: я знаю, что этот ключ уже существует (по какой-то причине, я просто вставил его, у меня где-то есть избыточность, что угодно). Я думаю, это было бы более интуитивно понятно.

Тем не менее, в чем преимущество выполнения текущего поведения с оператором [] (и только для этого я согласен, что должна быть функция с текущим поведением, а не оператор [])? Может быть, это даст более ясный код таким образом? Я не знаю.

Другой ответ состоял в том, что он уже существовал таким образом, так почему бы не сохранить его, но тогда, возможно, когда они (те, что были до stl) решили реализовать его таким образом, который, как они обнаружили, давали преимущество или что-то еще? Таким образом, мой вопрос в основном: почему вы решили реализовать его таким образом, что означает некоторую непоследовательность с другим оператором []. Какую пользу это дает?

Спасибо

Ответы [ 11 ]

21 голосов
/ 28 октября 2009

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

Если вам не нравится это поведение, вы можете использовать map::find вместо этого. Он возвращает итератор вместо значения. Это позволяет ему возвращать специальный итератор, когда значение не найдено (оно возвращает map::end), но также требует, чтобы вы разыменовали итератор, чтобы получить значение.

11 голосов
/ 28 октября 2009

Стандарт говорит (23.3.1.2/1), что оператор [] возвращает (*((insert(make_pair(x, T()))).first)).second. Вот в чем причина. Возвращает ссылку T&. Нет способа вернуть неверную ссылку. И это возвращает ссылку, потому что это очень удобно, я думаю, не так ли?

8 голосов
/ 28 октября 2009

Чтобы ответить на ваш настоящий вопрос: нет убедительного объяснения, почему это было сделано именно так. "Просто так".

Поскольку std::map является ассоциативным контейнером , нет четкого заранее определенного диапазона ключей, которые должны существовать (или не существовать) на карте (в отличие от совершенно другой ситуации с std::vector ). Это означает, что для std::map вам нужны как не вставлять, так и вставлять функции поиска. Можно было бы перегрузить [] без вставки и предоставить функцию для вставки. Или можно сделать наоборот: перегрузить [] в качестве оператора вставки и предоставить функцию для поиска без вставки. Итак, кто-то когда-то решил следовать последнему подходу. Это все, что нужно.

Если бы они сделали это наоборот, возможно, сегодня кто-то задал бы здесь обратную версию вашего вопроса.

4 голосов
/ 28 октября 2009

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

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

4 голосов
/ 28 октября 2009

Это для целей присвоения:


void test()
{
   std::map<std::string, int >myMap;
   myMap["hello"] = 5;
}
3 голосов
/ 18 декабря 2009

map.insert (ключ, элемент); удостоверяется, что ключ находится на карте, но не перезаписывает существующее значение.

map.operator [key] = item; удостоверяется, что ключ находится в карте и перезаписывает любое существующее значение элементом.

Обе эти операции достаточно важны, чтобы гарантировать одну строку кода. Разработчики, вероятно, выбрали, какая операция была более интуитивной для оператора [], и создали вызов функции для другой.

3 голосов
/ 28 октября 2009

Позволяет вставлять новые элементы с operator[], например:

std::map<std::string, int> m;
m["five"] = 5;

5 присваивается значению, возвращаемому m["five"], которое является ссылкой на вновь созданный элемент. Если operator[] не вставит новые элементы, это не сработает.

1 голос
/ 28 октября 2009

Ответ в том, что они хотели, чтобы реализация была удобной и быстрой.

Базовой реализацией вектора является массив. Таким образом, если в массиве 10 записей и вам нужна запись 5, функция T & vector :: operator [] (5) просто возвращает headptr + 5. Если вы запрашиваете ввод 5400, он возвращает headptr + 5400.

Базовой реализацией карты обычно является дерево. Каждый узел распределяется динамически, в отличие от вектора, который стандарт должен быть смежным. Таким образом, nodeptr + 5 ничего не значит, а map ["некоторая строка"] не означает rootptr + offset ("некоторая строка").

Как найти с картами, вектор имеет getAt (), если вы хотите проверить границы. В случае векторов проверка границ считалась ненужной для тех, кто этого не хотел. В случае карт единственный способ не вернуть ссылку - это сгенерировать исключение, и это также считалось ненужной ценой для тех, кто этого не хотел.

1 голос
/ 28 октября 2009

Разница здесь в том, что карта хранит «индекс», то есть значение, хранящееся в карте (в ее базовом дереве RB), составляет std::pair, а не просто «индексированное» значение. Всегда есть map::find(), который скажет вам, существует ли пара с данным ключом.

0 голосов
/ 28 июня 2013

Невозможно избежать создания объекта, потому что оператор [] не знает, как его использовать.

myMap["apple"] = "green";

или

char const * cColor = myMyp["apple"];

Я предлагаю, чтобы контейнер карты добавил функцию типа

if( ! myMap.exist( "apple")) throw ...

это намного проще и лучше читать, чем

if( myMap.find( "apple") != myMap.end()) throw ...

...