web-dev-qa-db-fra.com

Dans les cartes STL, vaut-il mieux utiliser map :: insert que []?

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?

199
danio

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.

239
netjeff

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 [].

53
Greg Rogers

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.

34
Hawkeye Parker

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.

:)

19
Torlack

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:

  1. map[key] - créer un élément de carte avec une valeur par défaut.
  2. = 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).

14
anton_rh

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;
}

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

13
Rampal Chaudhary

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!

10
GutiMac

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.

9
Anthony Cramp

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);
}
2
rlbond

Voici un autre exemple montrant que operator[]écrase la valeur de la clé, si elle existe, mais .insertne 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 ) ;

}
1
bobobobo

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; 
} 
1
mechatroner

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é.

1
dk123