web-dev-qa-db-fra.com

Manière préférée / idiomatique d'insérer dans une carte

J'ai identifié quatre manières différentes d'insérer dans un std::map:

std::map<int, int> function;

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

Laquelle de ces méthodes est la méthode préférée/idiomatique? (Et y a-t-il une autre façon à laquelle je n'ai pas pensé?)

87
fredoverflow

Tout d'abord, operator[] et insert les fonctions membres ne sont pas équivalentes du point de vue fonctionnel:

  • Le operator[] va recherche pour la clé, insère une valeur construite par défaut si non trouvée, et retourne une référence à laquelle vous affectez une valeur. Évidemment, cela peut être inefficace si le mapped_type peut bénéficier d’être directement initialisé au lieu de construit et attribué par défaut. Cette méthode empêche également de déterminer si une insertion a bien eu lieu ou si vous avez uniquement écrasé la valeur d'une clé précédemment insérée.
  • La fonction membre insert n'aura aucun effet si la clé est déjà présente dans la carte et, bien qu'elle soit souvent oubliée, renvoie un std::pair<iterator, bool> qui peut être intéressant (notamment pour déterminer si l’insertion a bien été faite).

Parmi toutes les possibilités énumérées pour appeler insert, les trois sont presque équivalentes. Pour rappel, regardons insert signature dans la norme:

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

Alors, en quoi les trois appels sont-ils différents?

  • std::make_pair repose sur la déduction d’arguments de gabarit et peut (et dans ce cas volonté) produire quelque chose d’un type différent du type réel value_type de la carte, ce qui nécessitera un appel supplémentaire à std::pair template constructeur afin de convertir en value_type (ie: ajouter const à first_type)
  • std::pair<int, int> nécessite également un appel supplémentaire au constructeur de template de std::pair afin de convertir le paramètre en value_type (ie: ajouter const à first_type)
  • std::map<int, int>::value_type _ ne laisse absolument aucune place au doute, car il s'agit directement du type de paramètre attendu par la fonction membre insert.

En fin de compte, j’éviterais d’utiliser operator[] lorsque l’objectif est d’insérer, à moins que la construction et l’assignation par défaut du mapped_type, et que je me fiche de déterminer si une nouvelle clé a effectivement été insérée. Lors de l’utilisation de insert, construire un value_type est probablement la voie à suivre.

78
icecrime

A partir de C++ 11, vous avez deux options supplémentaires majeures. Tout d'abord, vous pouvez utiliser insert() avec la syntaxe d'initialisation de liste:

function.insert({0, 42});

Ceci est fonctionnellement équivalent à

function.insert(std::map<int, int>::value_type(0, 42));

mais beaucoup plus concis et lisible. Comme d'autres réponses l'ont noté, cela présente plusieurs avantages par rapport aux autres formes:

  • L'approche operator[] Nécessite que le type mappé soit assignable, ce qui n'est pas toujours le cas.
  • L'approche operator[] Peut écraser des éléments existants et ne vous donne aucun moyen de savoir si cela s'est produit.
  • Les autres formes de insert que vous répertoriez impliquent une conversion de type implicite, ce qui peut ralentir votre code.

L'inconvénient majeur est que ce formulaire exigeait auparavant que la clé et la valeur soient copiables, de sorte qu'il ne fonctionnerait pas avec, par exemple. une carte avec les valeurs unique_ptr. Cela a été corrigé dans la norme, mais le correctif n'a peut-être pas encore atteint l'implémentation de votre bibliothèque standard.

Deuxièmement, vous pouvez utiliser la méthode emplace():

function.emplace(0, 42);

Ceci est plus concis que toutes les formes de insert(), fonctionne bien avec des types à déplacement seulement comme unique_ptr, Et peut théoriquement être légèrement plus efficace (bien qu'un compilateur décent devrait optimiser la différence ). Le seul inconvénient majeur est que cela peut un peu surprendre vos lecteurs, car les méthodes emplace ne sont généralement pas utilisées de cette façon.

83
Geoff Romer

La première version:

function[0] = 42; // version 1

peut ou non insérer la valeur 42 dans la carte. Si la clé 0 Existe, elle en affectera 42 à cette clé, en écrasant la valeur de cette clé. Sinon, il insère la paire clé/valeur.

Les fonctions d'insertion:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4

par contre, ne faites rien si la clé 0 existe déjà dans la carte. Si la clé n'existe pas, la paire clé/valeur est insérée.

Les trois fonctions d'insertion sont presque identiques. std::map<int, int>::value_type Est le typedef de std::pair<const int, int>, Et std::make_pair() produit évidemment un std::pair<> Par le biais de la magie de déduction de modèle. Toutefois, le résultat final devrait être identique pour les versions 2, 3 et 4.

Lequel pourrais-je utiliser? Personnellement, je préfère la version 1; c'est concis et "naturel". Bien sûr, si son comportement de réécriture n’est pas souhaité, je préférerais donc la version 4, car elle nécessite moins de frappe que les versions 2 et 3. Je ne sais pas s’il existe un seul moyen de facto insérer des paires clé/valeur dans un std::map.

Une autre façon d’insérer des valeurs dans une carte via l’un de ses constructeurs:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());
9
In silico

J'ai effectué des comparaisons de temps entre les versions susmentionnées:

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

Il s'avère que les différences de temps entre les versions d'insert sont minimes.

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

Cela donne respectivement pour les versions (j'ai couru le fichier 3 fois, d'où les 3 décalages horaires consécutifs pour chacun):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 ms, 2078 ms, 2072 ms

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 ms, 2037 ms, 2046 ms

 map_W_3[it] = Widget(2.0);

2592 ms, 2278 ms, 2296 ms

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 ms, 2031 ms, 2027 ms

Par conséquent, les résultats entre différentes versions d’insert peuvent être négligés (n’ont cependant pas effectué de test d’hypothèse)!

La version map_W_3[it] = Widget(2.0); prend environ 10 à 15% de temps supplémentaire pour cet exemple en raison d'une initialisation avec le constructeur par défaut pour Widget.

3
user3116431

En bref, l’opérateur [] Est plus efficace pour la mise à jour des valeurs car il implique l’appel du constructeur par défaut du type valeur, puis lui attribue une nouvelle valeur, tandis que insert() est plus efficace pour l’ajout de valeurs.

L'extrait cité tiré de Effective STL: 50 façons spécifiques d'améliorer votre utilisation de la bibliothèque de modèles standard par Scott Meyers, l'article 24 pourrait vous aider.

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

Vous pouvez décider de choisir une version sans programmation générique, mais le fait est que je trouve ce paradigme (différencier "ajouter" et "mettre à jour") extrêmement utile.

2
galactica

Je viens de changer un peu le problème (carte de chaînes) pour montrer un autre intérêt d'insertion:

std::map<int, std::string> rancking;

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

le fait que le compilateur ne montre aucune erreur sur "rancking [1] = 42;" peut avoir un impact dévastateur!

1
jo_

Si vous voulez écraser l'élément avec la clé 0

function[0] = 42;

Autrement:

function.insert(std::make_pair(0, 42));
1
Viktor Sehr

Si vous voulez insérer un élément dans std :: map - utilisez la fonction insert (), et si vous voulez trouver un élément (par clé) et lui en attribuer - utilisez l'opérateur [].

Pour simplifier l’insertion, utilisez la bibliothèque boost :: assign, comme ceci:

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
1
Denis Shevchenko