На картах STL лучше использовать map:: insert, чем []?

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

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

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

Ответ 1

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

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(std::make_pair(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.

Ответ 2

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

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

Ответ 3

Еще одно замечание: std::map:

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

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

Ответ 4

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

:)

Ответ 5

Если ваше приложение имеет критическую скорость, я советую использовать оператор [], потому что он создает всего 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 usedOutput when [] operator is used

Ответ 6

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

Выражение map[key] = value представляет собой фактически две операции:

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

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

Операция

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

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

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

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

Спасибо!

Ответ 9

Следует отметить, что вы также можете использовать 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);
}

Ответ 10

Это довольно ограниченный случай, но, судя по комментариям, которые я получил, думаю, стоит отметить.

Я видел, как люди в прошлом использовали карты в форме

map< const key, const val> Map;

чтобы уклониться от случаев перезаписи случайной стоимости, но затем запустите запись в некоторых других битах кода:

const_cast< T >Map[]=val;

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

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

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

Ответ 11

Вот еще один пример, показывающий, что operator[] перезаписывает значение для ключа, если оно существует, но .insert не перезаписывает значение, если оно существует.

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

Ответ 12

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

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

Это довольно распространенная проблема, когда нам нужно сопоставить различные неидеальные объекты с некоторым id в диапазоне 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; 
}