Il y a quelque temps, j'ai discuté avec un collègue de la manière d'insérer des valeurs dans STL maps . J'ai préféré map[key] = value;
parce que cela semble naturel et qu'il est facile à lire, alors qu'il a préféré map.insert(std::make_pair(key, value))
Je lui ai juste posé la question et aucun d’entre nous ne se souvient de la raison pour laquelle l’insertion est meilleure, mais je suis sûr que ce n’était pas seulement une préférence de style mais plutôt une raison technique telle que l’efficacité. Le référence SGI STL indique simplement "À proprement parler, cette fonction membre n'est pas nécessaire: elle n'existe que par commodité."
Quelqu'un peut-il me dire cette raison ou suis-je en train de rêver qu'il y en a une?
Quand tu écris
map[key] = value;
il n'y a aucun moyen de dire si vous avez remplacé la value
pour key
, ou si vous a créé un nouveau key
avec value
.
map::insert()
ne créera que:
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;
}
Pour la plupart de mes applications, je ne me soucie généralement pas de créer ou de remplacer, alors j'utilise le plus facile à lire map[key] = value
.
Les deux ont une sémantique différente lorsqu'il s'agit de la clé déjà existante dans la carte. Donc, ils ne sont pas vraiment directement comparables.
Mais la version operator [] nécessite la construction par défaut de la valeur, puis l'attribution. Par conséquent, si cela coûte plus cher que la construction de la copie, ce sera plus cher. Parfois, la construction par défaut n’a pas de sens, et il serait alors impossible d’utiliser la version operator [].
Une autre chose à noter avec std::map
:
myMap[nonExistingKey];
créera une nouvelle entrée dans la carte, associée à nonExistingKey
initialisée à une valeur par défaut.
Cela m’a fait très peur la première fois que je l’ai vue (tout en me frappant la tête contre un méchant virus hérité du passé). Je ne m'y serais pas attendu. Pour moi, cela ressemble à une opération d'extraction et je ne m'attendais pas à un "effet secondaire". Préférez map.find()
lorsque vous accédez à votre carte.
Si les performances du constructeur par défaut ne sont pas un problème, veuillez utiliser la version plus lisible, pour l'amour de Dieu.
:)
insert
est préférable du point de vue de la sécurité des exceptions.
L'expression map[key] = value
est en réalité deux opérations:
map[key]
- créer un élément de carte avec une valeur par défaut.= value
- copier la valeur dans cet élément.Une exception peut se produire à la deuxième étape. En conséquence, l'opération ne sera que partiellement effectuée (un nouvel élément a été ajouté à la carte, mais cet élément n'a pas été initialisé avec value
). La situation lorsqu'une opération n'est pas terminée mais que l'état du système est modifié s'appelle l'opération avec "effet secondaire".
insert
l'opération donne une garantie forte, cela signifie qu'elle n'a pas d'effets secondaires ( https://en.wikipedia.org/wiki/Exception_safety ). insert
est complètement terminé ou laisse la carte dans un état non modifié.
http://www.cplusplus.com/reference/map/map/insert/ :
Si un seul élément doit être inséré, le conteneur ne change pas en cas d'exception (forte garantie).
Si votre application est critique pour la vitesse, je vous conseillerai d'utiliser l'opérateur [] car elle crée un total de 3 copies de l'objet d'origine, dont 2 sont des objets temporaires et seront détruits tôt ou tard en tant que.
Mais dans insert (), 4 copies de l'objet d'origine sont créées, dont 3 sont des objets temporaires (pas nécessairement "temporaires") et sont détruites.
Ce qui signifie un temps supplémentaire pour: 1. Allocation de mémoire d'un objet 2. Un appel de constructeur supplémentaire 3. Un appel de destructeur supplémentaire 4. Désallocation de mémoire d'un objet
Si vos objets sont grands, les constructeurs sont typiques, les destructeurs libèrent beaucoup de ressources, les points ci-dessus comptent encore plus. En ce qui concerne la lisibilité, je pense que les deux sont assez justes.
La même question m’est venue à l’esprit mais pas au-dessus de la lisibilité, mais de la rapidité. Voici un exemple de code par lequel j'ai appris à connaître le point que j'ai mentionné.
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;
}
Maintenant, dans c ++ 11, je pense que le meilleur moyen d'insérer une paire dans une carte STL est:
typedef std::map<int, std::string> MyMap;
MyMap map;
auto& result = map.emplace(3,"Hello");
Le résultat sera une paire avec:
Premier élément (result.first), pointe vers la paire insérée ou pointe vers la paire avec cette clé si la clé existe déjà.
Deuxième élément (result.second), true si l'insertion était correcte ou false, quelque chose s'est mal passé.
PS: Si vous n’avez pas de cas concernant la commande, vous pouvez utiliser std :: unordered_map;)
Merci!
Un piège avec map :: insert () est qu'il ne remplacera pas une valeur si la clé existe déjà dans la carte. J'ai vu du code C++ écrit par Java programmeurs où ils s'attendaient à ce que insert () se comporte de la même manière que Map.put () dans Java où les valeurs sont remplacées.
Une note est que vous pouvez également utiliser 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);
}
Voici un autre exemple montrant que operator[]
écrase la valeur de la clé, si elle existe, mais .insert
ne remplace pas la valeur, si elle existe.
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 ) ;
}
Le fait que la fonction std :: map insert()
n'écrase pas la valeur associée à la clé nous permet d'écrire un code d'énumération d'objet comme ceci:
string Word;
map<string, size_t> dict;
while(getline(cin, Word)) {
dict.insert(make_pair(Word, dict.size()));
}
C'est un problème assez commun quand nous avons besoin de mapper différents objets non uniques à des identifiants compris entre 0 et N. Ces identifiants peuvent être utilisés ultérieurement, par exemple, dans des algorithmes de graphes. Une alternative avec operator[]
semblerait moins lisible à mon avis:
string Word;
map<string, size_t> dict;
while(getline(cin, Word)) {
size_t sz = dict.size();
if (!dict.count(Word))
dict[Word] = sz;
}
C'est un cas plutôt limité, mais à en juger par les commentaires que j'ai reçus, je pense que cela vaut la peine d'être noté.
Dans le passé, j'ai vu des gens utiliser des cartes sous forme de
map< const key, const val> Map;
pour éviter les écrasements accidentels de valeurs, mais écrivez ensuite dans d'autres bits de code:
const_cast< T >Map[]=val;
Je me souviens que leur raison de faire cela était parce qu'ils étaient sûrs que, dans certains morceaux de code, ils n'allaient pas écraser les valeurs de carte; par conséquent, aller de l'avant avec la méthode plus "lisible" []
.
Je n'ai jamais eu de problèmes directs avec le code écrit par ces personnes, mais je suis fermement convaincu que jusqu'à aujourd'hui, les risques - aussi minimes soient-ils - ne devraient pas être pris quand ils peuvent être facilement évités.
Dans les cas où vous traitez avec des valeurs de carte que ne doit absolument pas être écrasées, utilisez insert
. Ne faites pas d'exceptions simplement pour des raisons de lisibilité.