L'operatore std :: unordered_map [] esegue l'inizializzazione zero per chiave non esistente?


25

Secondo cppreference.com, std::map::operator[]per il valore inesistente non viene inizializzata zero.

Tuttavia, lo stesso sito non menziona l'inizializzazione zero per std::unordered_map::operator[], tranne che ha un esempio che si basa su questo.

Naturalmente questo è solo un sito di riferimento, non lo standard. Quindi, il codice qui sotto è ok o no?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}

13
@ Ælex non puoi testare in modo affidabile se qualcosa è inizializzato
idclev 463035818

2
@ Ælex Non capisco davvero, come si può avere un non inizializzato std::optional?
idclev 463035818,

2
@ Ælex non c'è modo di verificare se un oggetto è inizializzato o meno perché qualsiasi operazione su un oggetto non inizializzato diversa dall'inizializzazione provoca un comportamento indefinito. Un std::optionaloggetto che non contiene alcun valore contenuto è comunque un oggetto inizializzato.
Bolov,

2
L'oggetto valore è inizializzato con valore, non inizializzato con zero. Per i tipi scalari questi sono gli stessi, ma per i tipi di classe sono diversi.
aschepler

@bolov Ho provato a provarlo ieri usando gnu 17 e std 17, e stranamente tutto ciò che ho ottenuto è stato zero inizializzazione. Ho pensato di std::optional has_valuetestarlo, ma fallisce, quindi credo che tu abbia ragione.
Ælex

Risposte:


13

A seconda del sovraccarico di cui stiamo parlando, std::unordered_map::operator[]equivale a [unord.map.elem]

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

(il sovraccarico che prende un riferimento di valore si sposta appena k in try_emplaceed è altrimenti identico)

Se un elemento esiste sotto chiave knella mappa, quindi try_emplacerestituisce un iteratore a quell'elemento e false. Altrimenti, try_emplaceinserisce un nuovo elemento sotto la chiave ke restituisce un iteratore a quello e true [unord.map.modifiers] :

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

Interessante per noi è il caso in cui non ci siano ancora elementi [unord.map.modifiers] / 6 :

Altrimenti inserisce un oggetto di tipo value_­typecostruito conpiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(il sovraccarico che prende un riferimento di valore si sposta kin forward_­as_­tuplee, di nuovo, è altrimenti identico)

Poiché value_typeè un pair<const Key, T> [unord.map.overview] / 2 , questo ci dice che il nuovo elemento della mappa sarà costruito come:

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

Poiché argsè vuoto quando proviene da operator[], ciò si riduce al nostro nuovo valore che viene costruito come membro del pairfrom no argomenti [pairs.pair] / 14 che è l'inizializzazione diretta [class.base.init] / 7 di un valore di tipo Tusando ()come inizializzatore che si riduce all'inizializzazione del valore [dcl.init] /17.4 . L'inizializzazione del valore di an intè zero inizializzazione [dcl.init] / 8 . E zero inizializzazione di un intinizializza naturalmente inta 0 [dcl.init] / 6 .

Quindi sì, il tuo codice è garantito per restituire 0 ...


21

Sul sito che hai collegato dice:

Quando viene utilizzato l'allocatore predefinito, ciò comporta che la chiave viene copiata dalla chiave e il valore mappato viene inizializzato.

Così la int valore è inizializzato :

Gli effetti dell'inizializzazione del valore sono:

[...]

4) in caso contrario, l'oggetto viene inizializzato a zero

Questo è il motivo per cui il risultato è 0.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.