В картах STL лучше использовать map :: insert, чем []? - PullRequest
197 голосов
/ 28 ноября 2008

Некоторое время назад у меня была дискуссия с коллегой о том, как вставить значения в STL map . Я предпочел map[key] = value; потому что это естественно и понятно для чтения, тогда как он предпочитал map.insert(std::make_pair(key, value))

Я только что спросил его, и никто из нас не может вспомнить причину, по которой вставка лучше, но я уверен, что это был не просто стиль, а техническая причина, например, эффективность. Ссылка SGI STL просто говорит: «Строго говоря, эта функция-член не нужна: она существует только для удобства».

Кто-нибудь может сказать мне эту причину, или я просто мечтаю, что она есть?

Ответы [ 12 ]

236 голосов
/ 29 ноября 2008

Когда вы пишете

map[key] = value;

невозможно определить, если вы заменили value на key, или создали новый key с value.

map::insert() создаст только:

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

Для большинства моих приложений меня обычно не волнует, что я создаю или заменяю, поэтому я использую более простой для чтения текст map[key] = value.

53 голосов
/ 28 ноября 2008

Они имеют разную семантику, когда дело доходит до ключа, уже существующего на карте. Так что они на самом деле не сопоставимы напрямую.

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

34 голосов
/ 14 мая 2009

Еще одна вещь, которую нужно отметить с std::map:

myMap[nonExistingKey]; создаст новую запись на карте с ключом nonExistingKey, инициализированным значением по умолчанию.

Это испугало меня до чертиков, когда я впервые увидел это (ударившись головой об ужасную наследственную ошибку) Не ожидал этого. Для меня это выглядит как операция get, и я не ожидал "побочного эффекта". Предпочитайте map.find() при получении с вашей карты.

19 голосов
/ 28 ноября 2008

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

:)

14 голосов
/ 22 октября 2015

insert лучше с точки зрения безопасности.

Выражение map[key] = value на самом деле является двумя операциями:

  1. map[key] - создание элемента карты со значением по умолчанию.
  2. = value - копирование значения в этот элемент.

Исключение может произойти на втором этапе. В результате операция будет выполнена только частично (новый элемент был добавлен в карту, но этот элемент не был инициализирован с value). Ситуация, когда операция не завершена, но состояние системы изменено, называется операцией с «побочным эффектом».

insert операция дает надежную гарантию, то есть не имеет побочных эффектов (https://en.wikipedia.org/wiki/Exception_safety). insert либо полностью выполнена, либо оставляет карту в неизмененном состоянии.

http://www.cplusplus.com/reference/map/map/insert/:

Если необходимо вставить один элемент, в контейнере нет изменений в случае исключения (строгая гарантия).

13 голосов
/ 02 июня 2013

Если ваше приложение критично к скорости, я советую использовать оператор [], поскольку он создает всего 3 копии исходного объекта, из которых 2 являются временными объектами и рано или поздно уничтожаются как.

Но в insert () создаются 4 копии исходного объекта, из которых 3 являются временными объектами (не обязательно "временными") и уничтожаются.

Что означает дополнительное время для: 1. Выделение памяти для одного объекта 2. Один дополнительный вызов конструктора 3. Один дополнительный вызов деструктора 4. Одно освобождение памяти объектов

Если ваши объекты большие, конструкторы типичны, деструкторы много освобождают от ресурсов, выше количество очков еще больше. Что касается читабельности, я думаю, что оба достаточно справедливы.

Мне вспомнился тот же вопрос, но не о читаемости, а о скорости. Вот пример кода, с помощью которого я узнал о точке, которую я упомянул.

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

Output when insert() is used Output when [] operator is used

10 голосов
/ 19 ноября 2014

Теперь в c ++ 11 я думаю, что лучший способ вставить пару в карту STL:

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

Результат будет парой с:

  • Первый элемент (result.first), указывает на вставленную пару или указывает на пара с этим ключом, если ключ уже существует.

  • Второй элемент (result.second), true, если вставка была правильной или false это что-то пошло не так.

PS: Если вы не обращаете внимания на порядок, вы можете использовать std :: unordered_map;)

Спасибо!

9 голосов
/ 29 ноября 2008

Гоча с map :: insert () заключается в том, что он не заменит значение, если ключ уже существует на карте. Я видел код C ++, написанный Java-программистами, где они ожидали, что insert () будет вести себя так же, как Map.put () в Java, где значения заменяются.

2 голосов
/ 25 марта 2009

Обратите внимание, что вы также можете использовать Boost.Assign :

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}
1 голос
/ 02 июля 2015

Тот факт, что функция std :: map insert() не перезаписывает значение, связанное с ключом, позволяет нам писать код перечисления объектов следующим образом:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

Это довольно распространенная проблема, когда нам нужно сопоставить разные неуникальные объекты с некоторыми идентификаторами в диапазоне 0..N. Эти идентификаторы могут быть позже использованы, например, в алгоритмах графа. Альтернатива с operator[] выглядела бы менее читабельной на мой взгляд:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
...