En C++ 17, std::map
et std::unordered_map
a obtenu un nouveau modèle de fonction membre: try_emplace()
. Ce nouvel ajout, proposé dans n4279 , se comporte de la même manière que emplace()
, mais présente les avantages suivants:
try_emplace()
ne se déplace pas des arguments rvalue si l'insertion ne se produit pas. Ceci est utile lors de la manipulation de cartes dont les valeurs sont des types à déplacement uniquement, tels que std::unique_ptr
.try_emplace()
traite la clé et les arguments du mapped_type
séparément, ce qui le rend un peu plus intuitif que les mutateurs génériques exprimés en termes de value_type
(qui est std::pair
).Compte tenu des avantages ci-dessus, utiliseriez-vous jamais emplace()
de C++ 11 au lieu de try_emplace()
de C++ 1z lors de l'écriture de code C++ 1z uniquement?
try_emplace
peut en effet remplacer la plupart des utilisations de emplace
, mais si vous avez un cas d'utilisation inhabituel d'un map
avec un type de clé non copiable et inamovible, try_emplace
ne fonctionnera pas car il copie ou déplace la clé. Dans ce cas, vous devez utiliser emplace
avec std::pair
's constructeur de construction par morceaux pour éviter les copies et les déplacements.
Même si votre type de clé est copiable et/ou mobile, la construction par morceaux est le seul moyen d'éviter de copier ou de déplacer la construction de la clé, il peut donc y avoir des cas où vous préférez cela à try_emplace
.
try_emplace
Ne prend pas non plus en charge la recherche hétérogène - il ne le peut pas, car il prend la clé.
Supposons que nous ayons un std::map<std::string, int, std::less<>> counts;
Et un std::string_view
sv
. Je veux faire l'équivalent de ++counts[std::string(sv)];
, mais je ne veux pas créer un std::string
Temporaire, ce qui est juste du gaspillage, surtout si la chaîne est déjà présente dans la carte. try_emplace
Ne peut pas vous aider. Au lieu de cela, vous feriez quelque chose comme
if(auto lb = counts.lower_bound(sv); lb != counts.end() && lb->first == sv) {
++lb->second;
}
else {
counts.emplace_hint(lb, sv, 1);
}
Je préférerais toujours try_emplace à emplace. Une différence cruciale est que try_emplace ne construira pas l'objet associé à la clé, si la clé existe déjà, ce qui augmentera les performances au cas où des objets de ce type coûteraient cher à créer
Par exemple, le code ci-dessous (exemple de https://github.com/PacktPublishing/Cpp17-STL-Cookbook/blob/master/Chapter02/efficient_insert_or_reassign_to_map.cpp )
#include <iostream>
#include <functional>
#include <list>
#include <map>
using namespace std;
struct billionaire {
string name;
double dollars;
string country;
};
int main()
{
list<billionaire> billionaires {
{"Bill Gates", 86.0, "USA"},
{"Warren Buffet", 75.6, "USA"},
{"Jeff Bezos", 72.8, "USA"},
{"Amancio Ortega", 71.3, "Spain"},
{"Mark Zuckerberg", 56.0, "USA"},
{"Carlos Slim", 54.5, "Mexico"},
// ...
{"Bernard Arnault", 41.5, "France"},
// ...
{"Liliane Bettencourt", 39.5, "France"},
// ...
{"Wang Jianlin", 31.3, "China"},
{"Li Ka-shing", 31.2, "Hong Kong"}
// ...
};
map<string, pair<const billionaire, size_t>> m;
for (const auto &b : billionaires) {
auto [iterator, success] = m.try_emplace(b.country, b, 1);
if (!success) {
iterator->second.second += 1;
}
}
for (const auto & [key, value] : m) {
const auto &[b, count] = value;
cout << b.country << " : " << count << " billionaires. Richest is "
<< b.name << " with " << b.dollars << " B$\n";
}
}
Pour le code ci-dessus
m.try_emplace(b.country, b, 1);
si l'insertion échoue, les paires ne seront pas construites, ce qui augmente les performances