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

Почему std:: map operator [] создает объект, если ключ не существует?

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

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

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

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

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

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

Спасибо

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

Он предназначен для целей назначения:


void test()
{
   std::map<std::string, int >myMap;
   myMap["hello"] = 5;
}

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

Ответ 10

Рассмотрим такой вход - 3 блока, каждый блок 2 строки, первая строка - количество элементов во втором:

5
13 20 22 43 146
4
13 22 43 146
5
13 43 67 89 146

Задача: рассчитать количество целых чисел, которые присутствуют во вторых строках всех трех блоков. (Для этого ввода образца выход должен быть 3 до 13, 43 и 146 присутствуют во вторых строках всех трех блоков)

Посмотрите, насколько хорош этот код:

int main ()
{
    int n, curr;
    map<unsigned, unsigned char> myMap;
    for (int i = 0; i < 3; ++i)
    {
        cin >> n;
        for (int j = 0; j < n; ++j)
        {
            cin >> curr;
            myMap[curr]++;
        }

    }

    unsigned count = 0;
    for (auto it = myMap.begin(); it != myMap.end(); ++it)
    {
        if (it->second == 3)
            ++count;
    }

    cout << count <<endl;
    return 0;
}

В соответствии со стандартом operator[] возвращает ссылку на (*((insert(make_pair(key, T()))).first)).second. Вот почему я мог написать:

myMap[curr]++;

и он вставил элемент с ключом curr и инициализировал значение на ноль, если ключ не присутствовал на карте. А также он увеличил значение, несмотря на то, что элемент был на карте или нет.

Посмотрите, как просто? Это хорошо, не так ли? Это хороший пример того, что это действительно удобно.

Ответ 11

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

myMap["apple"] = "green";

или

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

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

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

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

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