web-dev-qa-db-fra.com

Quelle est la différence entre unordered_map :: emplace et unordered_map :: insert en C ++?

Quelle est la différence entre std::unordered_map::emplace et std::unordered_map::insert en C++?

30
Harsh M. Shah

unordered_map::insert copie ou déplace une paire clé-valeur dans le conteneur. Il est surchargé pour accepter la référence à const ou une référence rvalue :

std::pair<iterator,bool> insert(const std::pair<const Key, T>& value);

template<class P>
std::pair<iterator,bool> insert(P&& value);

unordered_map::emplace vous permet d'éviter les copies ou déplacements inutiles en construisant l'élément en place. Il utilise un transfert parfait et un modèle variadique pour transmettre les arguments au constructeur de la paire clé-valeur :

template<class... Args>
std::pair<iterator,bool> emplace(Args&&... args);

Mais il y a beaucoup de chevauchement entre les deux fonctions. emplace peut être utilisé pour transmettre au constructeur copier/déplacer de la paire clé-valeur, ce qui permet de l'utiliser comme insert. Cela signifie que l'utilisation de emplace ne garantit pas que vous éviterez les copies ou les déplacements. De plus, la version de insert qui prend une référence rvalue est en fait un modèle et accepte n'importe quel type P de telle sorte que la paire clé-valeur est constructible à partir de P.

Scott Meyers dit:

En principe, les fonctions de mise en place devraient parfois être plus efficaces que leurs homologues d'insertion, et elles ne devraient jamais être moins efficaces.

( Edit: Howard Hinnant a couru certaines expériences qui montraient parfois que insert est plus rapide que emplace)

Si vous voulez vraiment copier/déplacer dans le conteneur, il peut être judicieux d'utiliser insert car vous êtes plus susceptible d'obtenir une erreur de compilation si vous passez des arguments incorrects. Vous devez être plus prudent lorsque vous passez les bons arguments aux fonctions de mise en place.

La plupart des implémentations de unordered_map::emplace entraînera l'allocation dynamique de mémoire pour la nouvelle paire même si la carte contient déjà un élément avec cette clé et que le emplace échouera. Cela signifie que s'il y a de bonnes chances qu'un emplace échoue, vous pouvez obtenir de meilleures performances en utilisant l'insertion pour éviter des allocations de mémoire dynamique inutiles.

Petit exemple:

#include <unordered_map>
#include <iostream>

int main() {
  auto employee1 = std::pair<int, std::string>{1, "John Smith"};

  auto employees = std::unordered_map<int, std::string>{};

  employees.insert(employee1);  // copy insertion
  employees.insert(std::make_pair(2, "Mary Jones"));  // move insertion 
  employees.emplace(3, "James Brown");  // construct in-place

  for (const auto& employee : employees)
    std::cout << employee.first << ": " << employee.second << "\n";
}

Edit2: Sur demande. Il est également possible d'utiliser unordered_map::emplace avec une clé ou une valeur qui prend plus d'un paramètre constructeur. En utilisant le std::pairconstructeur par morceaux vous pouvez toujours éviter les copies ou les déplacements inutiles.

#include <unordered_map>
#include <iostream>

struct Employee {
  std::string firstname;
  std::string lastname;
  Employee(const std::string& firstname, const std::string& lastname) 
  : firstname(firstname), lastname(lastname){}    
};

int main() {
  auto employees = std::unordered_map<int, Employee>{};
  auto employee1 = std::pair<int, Employee>{1, Employee{"John", "Smith"}};

  employees.insert(employee1);  // copy insertion
  employees.insert(std::make_pair(2, Employee{"Mary", "Jones"}));  // move insertion
  employees.emplace(3, Employee("Sam", "Thomas")); // emplace with pre-constructed Employee
  employees.emplace(std::piecewise_construct,
                    std::forward_as_Tuple(4),
                    std::forward_as_Tuple("James", "Brown"));  // construct in-place
}
41
Chris Drew