2012-05-14 6 views
24

Ich bin ein wenig von std::map::insert ‚s Semantik verwirrt. Ich meine, ich beschwere mich nicht - der Standard ist der Standard und die API ist so wie sie ist. Dennoch wirdRationale für die C++ - Standard-Map-Insert-Semantik?

insert

die Einfügeoperation Prüfungen für jedes Element eingefügt ob ein weiteres Element bereits in dem Behälter mit dem gleichen Schlüssel Wert vorhanden ist, wenn dem so ist, wird das Element nicht eingesetzt und dessen zugeordneter Wert ist nicht in irgendeiner Weise verändert.

Und - nur in seinem Single-Argumente Version pair<iterator,bool> insert (const value_type& x); wird es Ihnen auch sagen, wenn es auch den (neuen, möglicherweise unterschiedlichen) Wert auf die Taste (n) eingefügt. Soweit ich verstehe, werden die Iterator Versionen leise Einfügungen ignorieren, wenn der Schlüssel bereits vorhanden ist.

Für mich ist dies einfach Zähler intuitiv, Ich hätte erwartet, dass der Wert Teil überschrieben werden und der alte Wert Teil auf Einfügen verworfen werden. Offensichtlich ist die Designer des STL dachten anders - jemand kennt die (historische) Begründung oder eine gründliche Erklärung dafür geben, wie die bestehende Semantik (mehr) sinnvoll?

Am Beispiel:

Es gibt ein paar grundlegenden Möglichkeiten, Insert in einem einzigen Schlüsselkarte wie std::map zu implementieren:

  • einfügen, ersetzen, wenn bereits
  • Einsatz vorhanden ist, ignorieren, wenn bereits vorhanden ist (dies ist das Verhalten von std :: map)
  • Einsatz, Fehler melden, wenn bereits UB vorhanden
  • Einsatz, wenn bereits vorhanden

ich jetzt versuchen, zu verstehen, warum insert_or_ignore mehr Sinn als insert_or_replace (oder insert_or_error) macht!


Ich schaute in meine Kopie von TC++PL (leider nur ich die deutsche Ausgabe haben), und interessanterweise Stroustrup schreibt in Kapitel 17.4.1.7 (Listenoperationen für Karte): (sorry Rohübersetzung aus Deutsch)

(...) Normalerweise kümmert man sich nicht, ob ein Schlüssel (sic!) neu eingefügt oder bereits vor dem Aufruf von insert() (...) existierte

Welche, so scheint mir, nur für Satz gilt, und nicht für Karte, denn für eine Karte macht es einen großen Unterschied, ob der angegebene Wert eingefügt wurde oder der alte in der Karte bleibt . (Es ist egal offensichtlich nicht für den Schlüssel, wie ein Äquivalent ist.

)

Hinweis: Ich weiß über operator[] und ich weiß, über Artikel 24 des Effective STL und die dort vorgeschlagenen efficientAddOrUpdate Funktion. Ich bin nur neugierig auf eine Begründung in insert Semantik, weil ich persönlich finde sie gegen intuitiv.

+5

Nun, Sie haben nicht aufgefordert, einen vorhandenen Wert zu ändern, Sie haben einen neuen Wert eingegeben. Ich stimme zu, dass ein konsequenteres Berichtsversagen eine gute Sache gewesen wäre. Sie können den zurückgegebenen Iterator immer noch dereferenzieren und prüfen, ob der neue Wert oder ein alter Wert vorhanden ist, oder diesen Iterator sogar verwenden, um den vorhandenen Wert zu aktualisieren. –

+2

Wenn Sie ersetzen/erstellen möchten, verwenden Sie den 'operator []'. – BoBTFish

+0

Hier ist ein Code zu ["kräftig einfügen"] (http://stackoverflow.com/a/8337563/596781) in eine Karte. –

Antwort

5

Ich weiß nicht über eine offizielle Begründung, aber ich würde die Dualität mit operator[] notieren.

Es scheint offensichtlich, dass man die zwei Varianten des Einsatzes möchte:

  • rein additiven
  • Additiv/destruktiv

Wenn wir eine map als spärliche Darstellung eines Arrays zu sehen, dann macht die Anwesenheit von operator[] Sinn. Ich weiß nicht, ob bereits existierende Wörterbücher existierten und diktierte diese Syntax (vielleicht, warum nicht).

Auch alle STL-Container haben mehrere Überladungen von insert, und diese Ähnlichkeit der Schnittstellen ermöglicht generische Programmierung.

Daher haben wir mindestens zwei Konkurrenten für die API: operator[] und insert.

nun in C++, wenn Sie lesen:

array[4] = 5; 

es naturaly, dass der Inhalt der Zelle bei Index 4 destruktiv aktualisiert wurde. Daher sollte map::operator[] einen Verweis zurückgeben, um dieses destruktive Update zu ermöglichen.

An dieser Stelle brauchen wir jetzt auch eine rein additive Version, und wir haben diese insert Methode herumliegen. Warum nicht ?

Natürlich man könnte insert die gleiche Semantik wie operator[] gegeben haben, und dann gehen Sie vor und implementieren eine insert_or_ignore Methode auf. Dies wäre jedoch mehr Arbeit gewesen.

Daher, während ich damit einverstanden, dass es überraschend sein könnte, ich glaube, meine Argumentation nicht zu fehlerhaft und kann eine wahrscheinliche Erklärung der Umstände, die uns hier führen :)


Im Hinblick auf die Alternativen, die Sie vorgeschlagen :

  • Einsatz, UB, wenn bereits vorhanden

Glücklicherweise ist es nicht!

  • Einsatz, Fehler melden, wenn bereits
  • existiert

Nur Java (und Derivate) ist Ausnahme-verrückt. C++ wurde in einer Zeit konzipiert, in der Ausnahmen für außergewöhnliche Umstände verwendet wurden.

  • einfügen, ersetzen, wenn bereits
  • Einsatz vorhanden ist, ignorieren, wenn bereits vorhanden ist (dies ist das Verhalten von std :: map)

Wir sind uns einig, dass die Wahl zwischen einer war von diesen. Beachten Sie, dass obwohl map die zweite Option gewählt hat, es nicht vollständig ignorieren die Tatsache, dass das Element bereits vorhanden war, zumindest in der Einzelelement-Version, da es warnt Sie, dass das Element nicht eingefügt wurde.

+0

+1, obwohl ich mir nicht sicher bin, ob wir wirklich eine rein additive Funktion brauchen, da wir 'find()' immer zuerst aufrufen können. (mit beiden 'einfügen' entweder/oder' op [] 'Aber vielleicht war die Dualität ein guter Grund. –

+0

@Martin: Ich stimme zu, dass ein 'find' funktioniert hätte, um eine rein additive Funktion zu erstellen. Es hätte jedoch einige Kesselplatte benötigt. –

+0

Ja, interessanterweise, ähnliche Boilerplate (oder Helper Fn) Sie brauchen jetzt für eine effiziente insert_or_replace :-) –

0

Von insert() erwarten Sie nicht, dass vorhandene Objekte im Container berührt werden. Deshalb berührt es sie einfach nicht.

+1

Beantwortet nicht das * Warum es keinen Fehler meldet? * Teil der Frage, aber ja, es beantwortet einen Teil der Frage.Das Verhalten der Funktion ist ziemlich intuitiv, erwartet einen 'einfügen' Aufruf zu modifizieren vorhanden Elemente wären eher gegensätzlich. –

+2

Nichts für ungut, aber Sie plappern die definierte Semantik der Funktion zurück, ohne eine Begründung zu liefern. * Warum * erwarte ich nicht, dass bestehende Elemente berührt werden? Weil es "einfügen" heißt und nicht "insert_or_replace" oder was? Wiederum * für mich * war es kontraintuitiv, und es hilft mir nicht zu verstehen, wenn mir gesagt wird: "Nein, das ist es nicht". :-) –

+0

@Martin: Wäre nicht 'insert_or_ignore' (wie in SQLite) ein besserer Name als' insert_or_replace'? – dan04

0

pair<iterator,bool> < - sagt der Bool-Teil nicht, ob die Einfügung erfolgreich ist oder nicht?

Sie können einfach den Wertteil des zurückgegebenen Iterators aktualisieren, wenn der Bool-Teil falsch ist, um das vorhandene Element mit demselben Schlüssel zu aktualisieren.

+0

Die Frage erwähnt dies. Sie haben den Teil der Frage ignoriert, in dem die anderen vier Überladungen von 'std :: map :: insert 'erwähnt wurden. –

7

Die Insert-Methode ist einfach nicht das, was Sie suchen, es klingt wie ... Die Insert-Methode ist gemacht, um genau das zu tun, was der Name impliziert ... Werte einfügen. Ich stimme zu, dass die Fähigkeit, einen Wert zu erstellen, wenn einer nicht bereits vorhanden ist, und den vorhandenen ersetzen, ansonsten in manchen Situationen wichtig ist, aber in anderen Fällen würden Sie einfach eher keine Ausnahmen, Rückgabewerte usw. behandeln möchte nur eine Einfügung durchführen, wenn der Wert nicht bereits vorhanden ist.

Es klingt wie die Methode, die Sie suchen (wie BoBTFish oben angezeigt) ist wahrscheinlich der [] Operator. Verwenden Sie sie einfach wie folgt:

myMap["key"] = "value"; 

Dies wird durch Ihre Karte gehen und den Schlüssel „Schlüssel“ finden, und ersetzen Sie den entsprechenden Wert mit „Wert“. Wenn der Schlüssel nicht vorhanden ist, wird er erstellt. Beide Methoden sind sehr nützlich in verschiedenen Situationen, und ich habe mich beide nur je nach dem, was ich brauche.

+6

Ich habe deinen Beitrag bearbeitet. Erstens, nicht (explizit) signieren, es wird missbilligt (Ihr Benutzer ist immer noch sichtbar); Zweitens, überprüfen Sie bitte die Markdown-Syntax, um zu erfahren, wie Sie Inline-Code-Snippets und Multiline-Code-Snippets formatieren. Danke für deine Antwort und willkommen auf SO :) –

2

Ich behaupte nicht, die ursprüngliche Begründung für die Entscheidung zu kennen, aber es ist nicht zu schwer, eins zu machen. Ich denke ;-)

Das aktuelle Verhalten von "Einfügen oder Ignorieren" macht es sehr einfach, die anderen beiden zu implementieren - zumindest für diejenigen von uns, die nicht über Erstellen und Verwenden von Nichtmitgliedsfunktionen zu ergänzen sind Standardbibliotheksfunktionalität ("es ist nicht OOP-y genug!").

Beispiel (auf der Stelle geschrieben, so dass Fehler vorhanden sein können):

template<typename Map> 
void insert_or_update(Map& map, typename Map::value_type const& x) 
{ 
    std::pair<typename Map::iterator, bool> result = map.insert(x); 
    if (!result.second) 
    result.first->second = x.second; // or throw an exception (consider using 
            // a different function name, though) 
} 

beachten Sie, dass wie es ist, die Funktion oben wirklich unterscheidet sich nicht wesentlich von operator[] - ja, vermeidet es Standardinitialisierung , aber zur gleichen Zeit (weil ich faul bin), schlägt es fehl, aus der Verschiebungssemantik Kapital zu schlagen, die Ihre aktuelle STL wahrscheinlich bereits für operator[] unterstützt.

In jedem anderen Einfügeverhalten für map wäre es mühsamer gewesen, die anderen zu implementieren, da map::find nur ein Ende sentinel zurückgibt, wenn der Schlüssel nicht bereits in der Karte ist. Mit Hilfe von <algorithm> (und speziell lower_bound) wäre es natürlich immer noch möglich, performante Zusatzfunktionen zu schreiben, ohne dass sie Implementierungsdetails und hässliche generische Konstrukte wie Schleifen übergehen ;-).

Verwandte Themen