Czy operator std :: unordered_map [] wykonuje inicjalizację zera dla nieistniejącego klucza?

25

Według cppreference.com, std::map::operator[]dla nieistniejącej wartości robi zerową inicjalizację.

Jednak ta sama strona nie wspomina o zerowej inicjalizacji std::unordered_map::operator[], z wyjątkiem tego, że opiera się na tym przykładzie.

Oczywiście jest to tylko strona referencyjna, a nie standard. Czy poniższy kod jest w porządku, czy nie?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
hyde
źródło
13
@ Ælex nie można wiarygodnie przetestować, jeśli coś jest zainicjowane
idclev 463035818,
2
@ Ælex Naprawdę nie rozumiem, jak możesz mieć niezainicjowany std::optional?
idclev 463035818
2
@ Ælex nie ma możliwości sprawdzenia, czy obiekt został zainicjowany, czy nie, ponieważ jakakolwiek operacja na niezainicjowanym obiekcie inna niż inicjalizacja powoduje niezdefiniowane zachowanie. std::optionalObiekt, który posiada zawartą wartość nie jest wciąż zainicjowany obiekt.
bolov
2
Obiekt wartości jest inicjowany wartością, a nie inicjowany zerem. Dla typów skalarnych są one takie same, ale dla typów klas są różne.
aschepler
@bolov Próbowałem to wczoraj przetestować przy użyciu GNU 17 i STD 17, i dziwnie wszystko, co dostałem, to zero inicjalizacji. Myślałem, że std::optional has_valueto przetestuję, ale to się nie powiedzie, więc myślę, że masz rację.
Ælex

Odpowiedzi:

13

W zależności od przeciążenia, o którym mówimy, std::unordered_map::operator[]jest równoważne z [unord.map.elem]

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

(przeciążenie przyjmujące wartość odniesienia po prostu przenosi księ try_emplacei jest poza tym identyczne)

Jeśli element istnieje pod kluczem kna mapie, to try_emplacezwraca iterator do tego elementu i false. W przeciwnym razie try_emplacewstawia nowy element pod klucz ki zwraca iterator do tego i true [unord.map.modifiers] :

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

Interesujący jest dla nas brak elementu [unord.map.modifiers] / 6 :

W przeciwnym razie wstawia obiekt typu value_­typeskonstruowany za pomocąpiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(przeciążenie przyjmujące wartość odniesienia po prostu przenosi księ forward_­as_­tuplei znowu jest identyczne)

Ponieważ value_typejest to pair<const Key, T> [unord.map.overview] / 2 , oznacza to, że nowy element mapy zostanie skonstruowany jako:

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

Ponieważ argsjest pusty, gdy przychodzi z operator[], sprowadza się to do tego, że nasza nowa wartość jest konstruowana jako element składowy „pair no arguments [pairs.pair] / 14, który jest bezpośrednią inicjalizacją [class.base.init] / 7 wartości typu Tprzy użyciu ()jako inicjator sprowadzający się do inicjalizacji wartości [dcl.init] /17.4 . Inicjalizacja wartości intto inicjalizacja zerowa [dcl.init] / 8 . I zerowa inicjalizacja intnaturalnie inicjuje to intdo 0 [dcl.init] / 6 .

Więc tak, twój kod ma gwarancję zwrotu 0…

Michael Kenzel
źródło
21

Na stronie, którą podlinkowałeś, napisano:

Gdy używany jest domyślny alokator, klucz jest konstruowany z klucza i inicjowana jest wartość odwzorowana.

Więc intjest wartość zainicjowany :

Skutki inicjalizacji wartości są następujące:

[...]

4) w przeciwnym razie obiekt jest inicjowany na zero

Właśnie dlatego wynik jest 0.

Płomień
źródło