Оператор std :: unordered_map [] выполняет нулевую инициализацию для несуществующего ключа?

25

Согласно cppreference.com, std::map::operator[]для несуществующего значения выполняется нулевая инициализация.

Однако на том же сайте не упоминается нулевая инициализация, за std::unordered_map::operator[]исключением того, что у него есть пример, который опирается на это.

Конечно это просто ссылочный сайт, а не стандартный. Итак, приведенный ниже код в порядке или нет?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
Хайд
источник
13
@ Ælex, вы не можете надежно проверить, инициализировано ли что-то
idclev 463035818
2
@ Я не очень понимаю, как можно не инициализировать std::optional?
idclev 463035818
2
@ Ælex нет способа проверить, инициализирован объект или нет, потому что любая операция над неинициализированным объектом, кроме инициализации, приводит к неопределенному поведению. std::optionalОбъект , который содержит не содержащееся значение по - прежнему является инициализированным объект.
Болов
2
Объект значения инициализируется значением, а не инициализируется нулем. Для скалярных типов они одинаковы, но для типов классов они разные.
aschepler
@bolov Я пытался протестировать это вчера, используя gnu 17 и std 17, и все, что я получил, было почти нулевой инициализацией. Я думал, std::optional has_valueчто проверю это, но это не удастся, поэтому я думаю, что вы правы.
Declex

Ответы:

13

В зависимости от того, о какой перегрузке мы говорим, std::unordered_map::operator[]это эквивалентно [unord.map.elem]

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

(перегрузка принимает RValue-ссылку просто перемещается kв try_emplaceи в остальном идентичны)

Если элемент существует под ключом kна карте, то try_emplaceвозвращает итератор для этого элемента и false. В противном случае try_emplaceвставляет новый элемент под ключом kи возвращает итератор для него и true [unord.map.modifiers] :

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

Для нас интересен случай, когда еще не было элемента [unord.map.modifiers] / 6 :

В противном случае вставляет объект типа, созданного value_­typeсpiecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(перегрузка принимает RValue-ссылку просто перемещается kв forward_­as_­tupleи, опять же , в остальном идентичны)

Поскольку value_typeэто pair<const Key, T> [unord.map.overview] / 2 , это говорит нам о том, что новый элемент карты будет построен как:

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

Так argsкак при поступлении из него пусто operator[], это сводится к тому, что наше новое значение создается как член pairиз аргументов from no [pair.pair] / 14, что является прямой инициализацией [class.base.init] / 7 значения типа, Tиспользуя ()как инициализатор, который сводится к инициализации значения [dcl.init] /17.4 . Значением инициализации intявляется нулевая инициализация [dcl.init] / 8 . И нулевая инициализация intестественно инициализирует это intк 0 [dcl.init] / 6 .

Так что да, ваш код гарантированно вернет 0 ...

Майкл Кензел
источник
21

На сайте, на который вы ссылаетесь, написано:

Когда используется распределитель по умолчанию, это приводит к тому, что ключ копируется из ключа, а сопоставленное значение инициализируется значением.

Таким образом, intэто значение инициализируется :

Эффекты инициализации значения:

[...]

4) в противном случае объект инициализируется нулями

Вот почему результат 0.

полыхать
источник