2008-11-28 3 views
182

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

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

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

+2

Спасибо за все замечательные ответы - они были действительно полезны. Это отличная демонстрация переполнения стека в лучшем виде. Я был разорван относительно того, какой должен быть принятый ответ: netjeff более подробно описывает различное поведение, Грег Роджерс упомянул проблемы производительности. Хотел бы я пометить оба. – danio 2008-12-05 11:36:41

+5

На самом деле, с C++ 11, вам, вероятно, лучше всего использовать [map :: emplace] (http://en.cppreference.com/w/cpp/container/map/emplace), который избегает двойной конструкции – einpoklum 2014-05-22 15:34:18

+0

@ einpoklum: На самом деле, Скотт Майерс предлагает иначе в своем разговоре «Развивающийся поиск эффективного C++». – 2015-11-19 21:40:50

ответ

219

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

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.

49

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

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

+1

make_pair может потребоваться конструктор копирования - это будет хуже, чем значение по умолчанию. +1 в любом случае. – Arkadiy 2008-11-28 16:04:22

+1

Главное, как вы сказали, что у них разная семантика. Таким образом, ни один не лучше другого, просто используйте тот, который делает то, что вам нужно. – jalf 2008-11-28 16:20:54

17

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

:)

7

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

2

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

Другая вещь, чтобы отметить с std::map:

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

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

1

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

Я видел людей в последние карты используют в виде

map< const key, const val> Map; 

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

const_cast<T>Map[]=val; 

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

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

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

1

Вот еще один пример, показывающий, что 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) ; 

} 
13

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

8

Сейчас в C++ 11 Я думаю, что лучший способ, чтобы вставить пару в карте СТЛ является:

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

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

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

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

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

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

Спасибо!

1

Тот факт, что станд :: Карта 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; 
} 
11

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/:

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