Nelle mappe STL, è meglio usare map :: insert di ?

Qualche tempo fa, ho avuto una discussione con un collega su come inserire valori nelle mappe STL. Ho preferito la map[key] = value; perché sembra naturale ed è chiaro da leggere mentre preferiva map.insert(std::make_pair(key, value))

Gliel’ho appena chiesto e nessuno di noi riesce a ricordare il motivo per cui l’inserimento è migliore, ma sono sicuro che non si trattava solo di una preferenza di stile, ma c’era una ragione tecnica come l’efficienza. Il riferimento SGI STL dice semplicemente “Strettamente parlando, questa funzione membro non è necessaria: esiste solo per comodità”.

Qualcuno può dirmi questa ragione, o sto solo sognando che ce n’è uno?

Quando scrivi

 map[key] = value; 

non c’è modo di sapere se hai sostituito il value per la key o se hai creato una nuova key con value .

map::insert() creerà solo:

 using std::cout; using std::endl; typedef std::map MyMap; MyMap map; // ... std::pair 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; } 

Per la maggior parte delle mie app, di solito non mi interessa se sto creando o sostituendo, quindi uso la più facile da leggere map[key] = value .

I due hanno semantica diversa quando si tratta della chiave già esistente nella mappa. Quindi non sono realmente direttamente comparabili.

Ma la versione operator [] richiede il valore predefinito per la costruzione del valore, e quindi l’assegnazione, quindi se questo è più costoso allora copia la costruzione, allora sarà più costoso. A volte la costruzione predefinita non ha senso e quindi sarebbe imansible usare la versione operator [].

Un’altra cosa da notare con std::map :

myMap[nonExistingKey]; creerà una nuova voce nella mappa, con chiave per nonExistingKey inizializzata su un valore predefinito.

Questo mi ha spaventato a morte la prima volta che l’ho visto (mentre sbattevo la testa contro un insetto leggiadro). Non me l’aspettavo. Per me, sembra un’operazione get, e non mi aspettavo “l’effetto collaterale”. Preferisci map.find() quando si ottiene dalla mappa.

Se il successo prestazionale del costruttore predefinito non è un problema, per favore, per amore di dio, vai con la versione più leggibile.

🙂

Se la tua applicazione è veloce, consiglierò di usare l’operatore [] perché crea 3 copie totali dell’object originale, di cui 2 sono oggetti temporanei e, prima o poi, vengono distrutti come.

Ma in insert (), vengono create 4 copie dell’object originale da cui 3 sono oggetti temporanei (non necessariamente “temporaries”) e vengono distrutti.

Il che significa tempi supplementari per: 1. Una allocazione di memoria di oggetti 2. Una chiamata di costruttore extra 3. Una chiamata di distruttore in più 4. Una deallocazione di memoria di oggetti

Se i tuoi oggetti sono grandi, i costruttori sono tipici, i distruttori fanno un sacco di risorse liberando, sopra i punti contano ancora di più. Per quanto riguarda la leggibilità, penso che entrambi siano abbastanza giusti.

Mi è venuta in mente la stessa domanda, ma non la leggibilità, ma la velocità. Ecco un esempio di codice attraverso il quale sono venuto a conoscenza del punto che ho menzionato.

 class Sample { static int _noOfObjects; int _objectNo; public: Sample() : _objectNo( _noOfObjects++ ) { std::cout<<"Inside default constructor of object "<<_objectNo< map; map.insert( std::make_pair( 1, sample) ); //map[1] = sample; return 0; } 

Uscita quando viene usato insert ()Uscita quando viene utilizzato l'operatore []

insert è meglio dal punto di sicurezza delle eccezioni.

La map[key] = value espressione map[key] = value è in realtà due operazioni:

  1. map[key] – creazione di un elemento mappa con valore predefinito.
  2. = value – copia del valore in quell’elemento.

Può accadere un’eccezione al secondo passaggio. Come risultato l’operazione verrà eseguita solo parzialmente (un nuovo elemento è stato aggiunto alla mappa, ma quell’elemento non è stato inizializzato con il value ). La situazione in cui un’operazione non è completa, ma lo stato del sistema viene modificato, viene chiamata operazione con “effetto collaterale”.

insert operazione di insert dà una forte garanzia, significa che non ha effetti collaterali ( https://en.wikipedia.org/wiki/Exception_safety ). insert è completato o lascia la mappa in uno stato non modificato.

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

Se un singolo elemento deve essere inserito, non ci sono cambiamenti nel contenitore in caso di eccezione (garanzia forte).

Ora in c ++ 11 penso che il modo migliore per inserire una coppia in una mappa STL sia:

 typedef std::map MyMap; MyMap map; auto& result = map.emplace(3,"Hello"); 

Il risultato sarà una coppia con:

  • Primo elemento (risultato prima), punta alla coppia inserita o punta alla coppia con questa chiave se la chiave esiste già.

  • Secondo elemento (result.second), true se l’inserimento era corretto o falso qualcosa andava storto.

PS: se non hai caso sull’ordine puoi usare std :: unordered_map;)

Grazie!

Un getcha con map :: insert () è che non sostituirà un valore se la chiave esiste già nella mappa. Ho visto codice C ++ scritto da programmatori Java dove si aspettavano che insert () si comportasse allo stesso modo di Map.put () in Java, dove i valori vengono sostituiti.

Una nota è che puoi anche utilizzare Boost.Assign :

 using namespace std; using namespace boost::assign; // bring 'map_list_of()' into scope void something() { map my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6); } 

Ecco un altro esempio, mostrando che operator[] sovrascrive il valore per la chiave se esiste, ma .insert non sovrascrive il valore se esiste.

 void mapTest() { map m; for( int i = 0 ; i <= 2 ; i++ ) { pair::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::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::iterator iter = m.begin() ; iter !=m.end() ; ++iter ) printf( "%d=>%f\n", iter->first, iter->second ) ; } 

Questo è un caso piuttosto limitato, ma a giudicare dai commenti che ho ricevuto penso che valga la pena di notare.

Ho visto persone che in passato utilizzavano mappe sotto forma di

 map< const key, const val> Map; 

per eludere casi di sovrascrittura di valore accidentale, ma poi proseguire scrivendo in qualche altro bit di codice:

 const_cast< T >Map[]=val; 

La ragione per cui ho fatto questo perché ricordo era che erano sicuri che in questi pezzi di codice non avrebbero sovrascritto i valori delle mappe; quindi, procedendo con il metodo più ‘leggibile’ [] .

Non ho mai avuto problemi diretti con il codice che è stato scritto da queste persone, ma sento fortemente fino ad oggi che i rischi – per quanto piccoli – non dovrebbero essere presi quando possono essere facilmente evitati.

Nei casi in cui hai a che fare con valori di mappe che assolutamente non devono essere sovrascritti, usa insert . Non fare eccezioni solo per la leggibilità.

Il fatto che la funzione std :: map insert() non sovrascriva il valore associato alla chiave ci consente di scrivere codice di enumerazione degli oggetti come questo:

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

È un problema piuttosto comune quando dobbiamo mappare diversi oggetti non univoci a qualche ID nell’intervallo 0..N. Questi ID possono essere usati successivamente, ad esempio, negli algoritmi del grafico. Alternativa con l’ operator[] sembrerebbe meno leggibile secondo me:

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