205 votes

Dans les cartes STL, est-il préférable d'utiliser map::insert plutôt que [] ?

Il y a quelque temps, j'ai eu une discussion avec un collègue sur la façon d'insérer des valeurs dans STL cartes . J'ai préféré map[key] = value; parce qu'il est naturel et clair à lire, alors qu'il préférait map.insert(std::make_pair(key, value)) .

Je viens de lui poser la question et aucun de nous deux ne se souvient de la raison pour laquelle l'insert est meilleur, mais je suis sûr qu'il ne s'agit pas simplement d'une préférence de style, mais plutôt d'une raison technique telle que l'efficacité. Les Référence SGI STL dit simplement : "Strictement parlant, cette fonction membre n'est pas nécessaire : elle n'existe que pour des raisons de commodité".

Quelqu'un peut-il me donner cette raison, ou est-ce que je rêve qu'il y en a une ?

3 votes

Merci pour toutes les excellentes réponses - elles ont été très utiles. C'est une excellente démonstration de stack overflow à son meilleur. J'étais partagé quant à la réponse à retenir : netjeff est plus explicite sur les différences de comportement, Greg Rogers a mentionné les problèmes de performance. J'aurais aimé pouvoir cocher les deux.

6 votes

En fait, avec C++11, il est probablement préférable d'utiliser map::emplace ce qui évite la double construction

0 votes

@einpoklum : En fait, Scott Meyers suggère le contraire dans son exposé "The evolving search for effective C++".

242voto

netjeff Points 3633

Lorsque vous écrivez

map[key] = value;

il n'y a aucun moyen de savoir si vous remplacés les value pour key ou si vous créé une nouvelle key con value .

map::insert() ne fera que créer :

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 préoccupe généralement pas de savoir si je crée ou si je remplace, et j'utilise donc l'option plus facile à lire map[key] = value .

17 votes

Il convient de noter que map::insert ne remplace jamais les valeurs. Et dans le cas général, je dirais qu'il est préférable d'utiliser (res.first)->second au lieu de value également dans le second cas.

1 votes

J'ai mis à jour pour être plus clair que map::insert ne remplace jamais. J'ai laissé le else parce que je pense que l'utilisation de value est plus clair que l'itérateur. Ce n'est que si le type de la valeur avait un copy ctor ou un op== inhabituel que ce serait différent, et ce type causerait d'autres problèmes en utilisant des conteneurs STL comme map.

1 votes

map.insert(std::make_pair(key,value)) debe ser map.insert(MyMap::value_type(key,value)) . Le type renvoyé par make_pair ne correspond pas au type pris par insert et la solution actuelle nécessite une conversion

53voto

Greg Rogers Points 18119

Les deux ont des sémantiques différentes en ce qui concerne la clé déjà existante dans la carte. Ils ne sont donc pas directement comparables.

Mais la version de l'opérateur[] nécessite la construction par défaut de la valeur, puis l'affectation, de sorte que si cela est plus coûteux que la construction par copie, alors ce sera plus coûteux. Parfois, la construction par défaut n'a pas de sens, et il serait alors impossible d'utiliser la version operator[].

1 votes

Make_pair peut nécessiter un constructeur de copie - ce qui serait pire que le constructeur par défaut. +1 de toute façon.

1 votes

L'essentiel est, comme vous l'avez dit, qu'ils ont une sémantique différente. Aucun n'est donc meilleur que l'autre, il suffit d'utiliser celui qui répond à vos besoins.

0 votes

Pourquoi le constructeur par copie serait-il pire que le constructeur par défaut suivi d'une affectation ? Si c'est le cas, alors la personne qui a écrit la classe a raté quelque chose, parce que quoi que fasse l'opérateur=, elle aurait dû faire la même chose dans le constructeur de copie.

36voto

Hawkeye Parker Points 605

Une autre chose à noter concernant les std::map :

myMap[nonExistingKey]; créera une nouvelle entrée dans la carte, dont la clé est nonExistingKey initialisé à une valeur par défaut.

J'ai eu une peur bleue la première fois que je l'ai vu (en me cognant la tête contre un méchant bug de l'héritage). Je ne m'y attendais pas. Pour moi, cela ressemble à une opération de récupération, et je ne m'attendais pas à cet "effet secondaire". Préférer map.find() lors de l'obtention de votre carte.

3 votes

C'est une vue convenable, bien que les cartes de hachage soient assez universelles pour ce format. Il pourrait s'agir d'une de ces "bizarreries que personne ne trouve étranges", simplement parce que les mêmes conventions sont très répandues.

19voto

Torlack Points 2910
Si l'impact sur les performances du constructeur par défaut n'est pas un problème, s'il vous plaît, pour l'amour de Dieu, choisissez la version la plus lisible.

)

5 votes

Deuxièmement ! Il faut marquer ce point. Trop de gens échangent leur obtusité contre des accélérations de l'ordre de la nano-seconde. Ayez pitié de nous, pauvres âmes, qui devons entretenir de telles atrocités !

6 votes

Comme l'a écrit Greg Rogers : "Les deux ont une sémantique différente lorsqu'il s'agit de la clé déjà existante dans la carte. Ils ne sont donc pas directement comparables."

0 votes

Il s'agit d'une ancienne question-réponse. Mais "une version plus lisible" est une raison stupide. En effet, la version la plus lisible dépend de la personne.

13voto

Rampal Chaudhary Points 265

Si la vitesse de votre application est critique, je vous conseille d'utiliser l'opérateur [] car il crée au total 3 copies de l'objet original, dont 2 sont des objets temporaires qui seront tôt ou tard détruits.

Mais dans insert(), 4 copies de l'objet original sont créées, dont 3 sont des objets temporaires (pas nécessairement "temporaires") et sont détruites.

Ce qui signifie du temps supplémentaire pour : 1. L'allocation de la mémoire d'un objet 2. Un appel de constructeur supplémentaire 3. Un appel supplémentaire au destructeur 4. Une désallocation de la mémoire des objets

Si vos objets sont grands, que les constructeurs sont typiques et que 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 sur la lisibilité, mais sur la vitesse. Voici un exemple de code qui m'a permis de comprendre 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

5 votes

Exécutez à nouveau ce test en activant toutes les optimisations.

2 votes

Réfléchissez également à ce que fait l'opérateur []. Il recherche d'abord dans la carte une entrée correspondant à la clé spécifiée. S'il en trouve une, il remplace la valeur de cette entrée par celle spécifiée. Dans le cas contraire, il insère une nouvelle entrée avec la clé et la valeur spécifiées. Plus votre carte est grande, plus l'opérateur [] mettra de temps à la parcourir. À un moment donné, cela compensera largement l'appel supplémentaire à copy c'tor (s'il reste dans le programme final après que le compilateur a fait son travail d'optimisation).

1 votes

@antred, insert doit effectuer la même recherche, il n'y a donc pas de différence par rapport à [] (parce que les clés de la carte sont uniques).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X