Führt der Operator std :: unordered_map [] eine Nullinitialisierung für einen nicht vorhandenen Schlüssel durch?

25

Laut cppreference.com wird std::map::operator[]für nicht vorhandene Werte eine Nullinitialisierung durchgeführt .

Dieselbe Site erwähnt jedoch keine Nullinitialisierung für std::unordered_map::operator[], außer es gibt ein Beispiel, das sich darauf stützt.

Natürlich ist dies nur eine Referenzseite, nicht der Standard. Ist der folgende Code in Ordnung oder nicht?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
Hyde
quelle
13
@ Ælex Sie können nicht zuverlässig testen, ob etwas initialisiert ist
idclev 463035818
2
@ Ælex Ich verstehe nicht wirklich, wie kann man eine nicht initialisierte haben std::optional?
idclev 463035818
2
@ Ælex Es gibt keine Möglichkeit zu testen, ob ein Objekt initialisiert wurde oder nicht, da eine andere Operation an einem nicht initialisierten Objekt als der Initialisierung zu undefiniertem Verhalten führt. Ein std::optionalObjekt, das keinen enthaltenen Wert enthält, ist weiterhin ein initialisiertes Objekt.
Bolov
2
Das Wertobjekt ist wertinitialisiert und nicht nullinitialisiert. Für Skalartypen sind diese gleich, für Klassentypen jedoch unterschiedlich.
Aschepler
@bolov Ich habe gestern versucht, es mit Gnu 17 und Standard 17 zu testen, und bizarrerweise bekam ich nur eine Null-Initialisierung. Ich dachte, ich std::optional has_valuewürde es testen, aber es schlägt fehl, also denke ich, dass Sie richtig sind.
Ælex

Antworten:

13

Abhängig von der Überlastung, von der wir sprechen, std::unordered_map::operator[]entspricht dies [unord.map.elem]

T& operator[](const key_type& k)
{
    return try_­emplace(k).first->second;
}

(Die Überlastung, die eine r-Wert-Referenz nimmt, bewegt sich nur khinein try_emplaceund ist ansonsten identisch.)

Wenn ein Element unter Schlüssel kin der Karte vorhanden ist, wird try_emplaceein Iterator für dieses Element und zurückgegeben false. Andernfalls wird try_emplaceein neues Element unter den Schlüssel eingefügtk und ein Iterator an diesen und true [unord.map.modifiers] zurückgegeben :

template <class... Args>
pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);

Interessant für uns ist der Fall, dass es noch kein Element gibt [unord.map.modifiers] / 6 :

Andernfalls wird ein Objekt vom Typ value_­typeeingefügt, mit dem erstellt wurdepiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(Die Überlastung, die eine r-Wert-Referenz nimmt, bewegt sich nur khinein forward_­as_­tupleund ist ansonsten wieder identisch.)

Da value_typees sich um eine pair<const Key, T> [unord.map.overview] / 2 handelt , wird das neue Kartenelement wie folgt erstellt:

pair<const Key, T>(piecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...));

Da argses leer ist, wenn es von kommt operator[], läuft dies darauf hinaus, dass unser neuer Wert als Mitglied von pairfrom no arguments [pairs.pair] / 14 konstruiert wird, was eine direkte Initialisierung [class.base.init] / 7 eines Wertes vom Typ Tusing ist ()als Initialisierer, der auf die Wertinitialisierung hinausläuft [dcl.init] /17.4 . Die Wertinitialisierung von a intist die Nullinitialisierung [dcl.init] / 8 . Und die Nullinitialisierung von a initialisiert dies intnatürlich intauf 0 [dcl.init] / 6 .

Also ja, Ihr Code gibt garantiert 0 zurück…

Michael Kenzel
quelle
21

Auf der von Ihnen verlinkten Site heißt es:

Wenn der Standardzuweiser verwendet wird, wird der Schlüssel aus dem Schlüssel kopiert und der zugeordnete Wert wird initialisiert.

Das intist also wertinitialisiert :

Die Auswirkungen der Wertinitialisierung sind:

[...]

4) Andernfalls wird das Objekt mit Null initialisiert

Deshalb ist das Ergebnis 0.

Blaze
quelle