Quelle est la différence entre std::unordered_map::emplace
et std::unordered_map::insert
en C++?
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
.
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::pair
constructeur 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
}