L'opérateur std :: unordered_map [] effectue-t-il une initialisation à zéro pour une clé non existante?

25

Selon cppreference.com, std::map::operator[]pour une valeur non existante, l'initialisation à zéro est effectuée.

Cependant, le même site ne mentionne pas l'initialisation zéro pour std::unordered_map::operator[], sauf qu'il a un exemple qui s'appuie sur cela.

Bien sûr, ce n'est qu'un site de référence, pas la norme. Alors, le code ci-dessous est-il correct ou non?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
hyde
la source
13
@ Ælex vous ne pouvez pas tester de manière fiable si quelque chose est initialisé
idclev 463035818
2
@ Ælex je ne comprends pas vraiment, comment pouvez-vous avoir un non initialisé std::optional?
idclev 463035818
2
@ Ælex il n'y a aucun moyen de tester si un objet est initialisé ou non car toute opération sur un objet non initialisé autre que l'initialisation entraîne un comportement indéfini. Un std::optionalobjet qui ne contient aucune valeur contenue est toujours un objet initialisé.
bolov
2
L'objet value est initialisé en valeur, pas initialisé en zéro. Pour les types scalaires, ce sont les mêmes, mais pour les types de classe, ils sont différents.
aschepler
@bolov J'ai essayé de le tester hier en utilisant gnu 17 et std 17, et bizarrement tout ce que j'ai obtenu était une initialisation nulle. Je pensais std::optional has_valuele tester, mais il échoue, donc je suppose que vous avez raison.
Ælex

Réponses:

13

Selon la surcharge dont nous parlons, std::unordered_map::operator[]équivaut à [unord.map.elem]

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

(la surcharge prenant une référence rvalue se déplace simplement kdanstry_emplace et est par ailleurs identique)

Si un élément existe sous clé kdans la carte, try_emplaceretourne alors un itérateur à cet élément et false. Sinon, try_emplaceinsère un nouvel élément sous la clé k, et renvoie un itérateur à celui-ci et true [unord.map.modifiers] :

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

Intéressant pour nous est le cas où il n'y a pas encore d'élément [unord.map.modifiers] / 6 :

Sinon, insère un objet de type value_­typeconstruit avecpiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(la surcharge en prenant une référence rvalue se déplace juste ken forward_­as_­tupleet, encore une fois, est par ailleurs identique)

Puisque value_typec'est un pair<const Key, T> [unord.map.overview] / 2 , cela nous dit que le nouvel élément de carte sera construit comme:

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

Puisque argsest vide en venant de operator[], cela revient à notre nouvelle valeur étant construite en tant que membre de l' pairargument no [pairs.pair] / 14 qui est l'initialisation directe [class.base.init] / 7 d'une valeur de type Tusing ()comme initialiseur qui se résume à l'initialisation de la valeur [dcl.init] /17.4 . L'initialisation de la valeur d'un intest une initialisation nulle [dcl.init] / 8 . Et l'initialisation nulle de an intinitialise naturellement cela intà 0 [dcl.init] / 6 .

Alors oui, votre code est garanti pour retourner 0…

Michael Kenzel
la source
21

Sur le site que vous avez lié, il est écrit:

Lorsque l'allocateur par défaut est utilisé, cela entraîne la construction de la copie à partir de la clé et l'initialisation de la valeur mappée.

Donc, la valeurint est initialisée :

Les effets de l'initialisation de la valeur sont:

[...]

4) sinon, l'objet est initialisé à zéro

C'est pourquoi le résultat est 0.

Flamber
la source