Ich habe vier verschiedene Möglichkeiten zum Einfügen von Elementen in a identifiziert 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));
Welcher davon ist der bevorzugte / idiomatische Weg? (Und gibt es einen anderen Weg, an den ich nicht gedacht habe?)
Antworten:
Zu allererst
operator[]
undinsert
Elementfunktionen sind nicht funktional äquivalent:operator[]
wird die Suche nach dem Schlüssel, einen einfügen Standard konstruiert Wert , wenn nicht gefunden, und geben einen Hinweis auf die Sie einen Wert zuweisen. Offensichtlich kann dies ineffizient sein, wenn das Unternehmenmapped_type
davon profitieren kann, direkt initialisiert zu werden, anstatt standardmäßig erstellt und zugewiesen zu werden. Diese Methode macht es auch unmöglich festzustellen, ob tatsächlich eine Einfügung stattgefunden hat oder ob Sie nur den Wert für einen zuvor eingefügten Schlüssel überschrieben habeninsert
Elementfunktion hat keine Auswirkung, wenn der Schlüssel bereits in der Karte vorhanden ist, und gibt, obwohl er häufig vergessen wird, einen zurück,std::pair<iterator, bool>
der von Interesse sein kann (insbesondere, um festzustellen, ob das Einfügen tatsächlich erfolgt ist).Von allen aufgelisteten Anrufmöglichkeiten
insert
sind alle drei nahezu gleichwertig. Lassen Sie uns zur Erinnerung dieinsert
Signatur im Standard betrachten:Wie unterscheiden sich die drei Anrufe?
std::make_pair
Template - Argument Abzug beruht auf und konnte (und in diesem Fall wird ) produziert etwas von einem anderen Typ als die tatsächlichenvalue_type
von der Karte, das einen zusätzlichen Aufruf benötigtstd::pair
Vorlage Konstruktor , um zu konvertierenvalue_type
(dh: das Hinzufügenconst
zufirst_type
)std::pair<int, int>
erfordert auch einen zusätzlichen Aufruf des Vorlagenkonstruktors vonstd::pair
, um den Parameter in zu konvertierenvalue_type
(dh: Hinzufügenconst
zufirst_type
)std::map<int, int>::value_type
lässt absolut keinen Zweifel offen, da es sich direkt um den von der Elementfunktion erwarteten Parametertyp handeltinsert
.Am Ende würde ich es vermeiden, zu verwenden,
operator[]
wenn das Ziel das Einfügen ist, es sei denn, es entstehen keine zusätzlichen Kosten für das Standardkonstruieren und Zuweisen desmapped_type
und es ist mir egal, ob ein neuer Schlüssel effektiv eingefügt wurde. Bei der Verwendunginsert
ist das Konstruieren von avalue_type
wahrscheinlich der richtige Weg.quelle
Ab C ++ 11 haben Sie zwei wichtige zusätzliche Optionen. Erstens können Sie
insert()
mit der Listeninitialisierungssyntax verwenden:Dies ist funktional äquivalent zu
aber viel prägnanter und lesbarer. Wie andere Antworten festgestellt haben, hat dies gegenüber den anderen Formen mehrere Vorteile:
operator[]
Ansatz erfordert, dass der zugeordnete Typ zuweisbar ist, was nicht immer der Fall ist.operator[]
Ansatz kann vorhandene Elemente überschreiben und gibt Ihnen keine Möglichkeit zu erkennen, ob dies geschehen ist.insert
, die Sie auflisten, beinhalten eine implizite Typkonvertierung, die Ihren Code verlangsamen kann.Der Hauptnachteil besteht darin, dass für dieses Formular der Schlüssel und der Wert kopierbar sein müssen, sodass es beispielsweise mit einer Karte mit nicht funktioniert
unique_ptr
Werten . Dies wurde im Standard behoben, aber der Fix hat möglicherweise Ihre Standardbibliotheksimplementierung noch nicht erreicht.Zweitens können Sie die
emplace()
Methode verwenden:Dies ist prägnanter als jede andere Form von
insert()
, funktioniert gut mit Nur-Verschieben-Typen wieunique_ptr
und kann theoretisch etwas effizienter sein (obwohl ein anständiger Compiler den Unterschied optimieren sollte). Der einzige große Nachteil ist, dass es Ihre Leser ein wenig überraschen kann, daemplace
Methoden normalerweise nicht so verwendet werden.quelle
Die erste Version:
kann den Wert 42 in die Karte einfügen oder nicht. Wenn der Schlüssel
0
vorhanden ist, weist er diesem Schlüssel 42 zu und überschreibt den Wert, den dieser Schlüssel hatte. Andernfalls wird das Schlüssel / Wert-Paar eingefügt.Die Einfügefunktionen:
Tun Sie andererseits nichts, wenn der Schlüssel
0
bereits in der Karte vorhanden ist. Wenn der Schlüssel nicht vorhanden ist, wird das Schlüssel / Wert-Paar eingefügt.Die drei Einfügefunktionen sind nahezu identisch.
std::map<int, int>::value_type
ist dastypedef
fürstd::pair<const int, int>
undstd::make_pair()
produziert offensichtlich einstd::pair<>
Via-Template-Abzugsmagie. Das Endergebnis sollte jedoch für die Versionen 2, 3 und 4 gleich sein.Welches würde ich verwenden? Ich persönlich bevorzuge Version 1; es ist prägnant und "natürlich". Wenn das Überschreibverhalten nicht erwünscht ist, würde ich natürlich Version 4 bevorzugen, da weniger Eingaben erforderlich sind als in den Versionen 2 und 3. Ich weiß nicht, ob es de facto eine einzige Möglichkeit gibt, Schlüssel / Wert-Paare in a einzufügen
std::map
.Eine andere Möglichkeit, Werte über einen ihrer Konstruktoren in eine Karte einzufügen:
quelle
Wenn Sie das Element mit der Taste 0 überschreiben möchten
Andernfalls:
quelle
Da C ++ 17
std::map
zwei neue Einfügemethoden bietet:insert_or_assign()
undtry_emplace()
, wie auch im Kommentar von sp2danny erwähnt .insert_or_assign()
Grundsätzlich
insert_or_assign()
ist eine "verbesserte" Version vonoperator[]
. Im Gegensatz zuoperator[]
,insert_or_assign()
erfordert nicht den Wert Typ der Karte standardmäßig konstruierbar zu sein. Der folgende Code wird beispielsweise nicht kompiliert, da erMyClass
keinen Standardkonstruktor hat:Wenn Sie jedoch
myMap[0] = MyClass(1);
durch die folgende Zeile ersetzen , wird der Code kompiliert und das Einfügen erfolgt wie vorgesehen:Außerdem ähnelt
insert()
,insert_or_assign()
kehrt einpair<iterator, bool>
. Der Boolesche Wert gibt an,true
ob eine Einfügung erfolgt ist undfalse
ob eine Zuweisung vorgenommen wurde. Der Iterator zeigt auf das Element, das eingefügt oder aktualisiert wurde.try_emplace()
Ähnlich wie oben
try_emplace()
ist eine "Verbesserung" vonemplace()
. Im Gegensatz dazuemplace()
werdentry_emplace()
die Argumente nicht geändert, wenn das Einfügen aufgrund eines bereits in der Karte vorhandenen Schlüssels fehlschlägt. Der folgende Code versucht beispielsweise, ein Element mit einem Schlüssel zu ersetzen, der bereits in der Karte gespeichert ist (siehe *):Ausgabe (zumindest für VS2017 und Coliru):
Wie Sie sehen, zeigt
pMyObj
nicht mehr auf das ursprüngliche Objekt. Wenn Sie jedochauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
durch den folgenden Code ersetzen , sieht die Ausgabe anders aus, da siepMyObj
unverändert bleibt:Ausgabe:
Code auf Coliru
Bitte beachten Sie: Ich habe versucht, meine Erklärungen so kurz und einfach wie möglich zu halten, um sie in diese Antwort zu integrieren. Für eine genauere und umfassendere Beschreibung empfehle ich, diesen Artikel über Fluent C ++ zu lesen .
quelle
Ich habe einige Zeitvergleiche zwischen den oben genannten Versionen durchgeführt:
Es stellt sich heraus, dass die Zeitunterschiede zwischen den Insert-Versionen winzig sind.
Dies ergibt jeweils für die Versionen (ich habe die Datei 3 mal ausgeführt, daher die 3 aufeinander folgenden Zeitunterschiede für jede):
2198 ms, 2078 ms, 2072 ms
2290 ms, 2037 ms, 2046 ms
2592 ms, 2278 ms, 2296 ms
2234 ms, 2031 ms, 2027 ms
Daher können Ergebnisse zwischen verschiedenen Insert-Versionen vernachlässigt werden (es wurde jedoch kein Hypothesentest durchgeführt)!
Die
map_W_3[it] = Widget(2.0);
Version benötigt für dieses Beispiel aufgrund einer Initialisierung mit dem Standardkonstruktor für Widget etwa 10-15% mehr Zeit.quelle
Kurz gesagt, der
[]
Operator ist effizienter für die Aktualisierung von Werten, da er den Standardkonstruktor des Werttyps aufruft und ihm dann einen neuen Wert zuweistinsert()
er effizienter für das Hinzufügen von Werten ist.Das zitierte Snippet aus Effective STL: 50 spezifische Möglichkeiten zur Verbesserung der Verwendung der Standardvorlagenbibliothek von Scott Meyers, Punkt 24, könnte hilfreich sein.
Sie können sich für eine Version ohne generische Programmierung entscheiden, aber der Punkt ist, dass ich dieses Paradigma (Unterscheidung zwischen "Hinzufügen" und "Aktualisieren") äußerst nützlich finde.
quelle
Wenn Sie ein Element in std :: map einfügen möchten, verwenden Sie die Funktion insert (), und wenn Sie ein Element (nach Schlüssel) suchen und ihm ein Element zuweisen möchten, verwenden Sie den Operator [].
Verwenden Sie zur Vereinfachung des Einfügens die Datei boost :: assign wie folgt:
quelle
Ich ändere das Problem nur ein wenig (Map of Strings), um ein weiteres Interesse an Insert zu zeigen:
die Tatsache, dass der Compiler keinen Fehler bei "rancking [1] = 42;" kann verheerende Auswirkungen haben!
quelle
std::string::operator=(char)
vorhanden, aber sie zeigen einen Fehler für den letzteren, weil der Konstruktorstd::string::string(char)
nicht vorhanden ist. Es sollte keinen Fehler erzeugen, da C ++ jedes ganzzahlige Literal immer frei als interpretiert.char
Dies ist also kein Compiler-Fehler, sondern ein Programmiererfehler. Grundsätzlich sage ich nur, dass Sie selbst darauf achten müssen, ob dies einen Fehler in Ihrem Code verursacht oder nicht. Übrigens können Sie druckenrancking[0]
und ein Compiler mit ASCII wird ausgeben*
, das heißt(char)(42)
.