Чи робить оператор 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
@ Ælex Я дійсно не розумію, як у вас може бути неаніціалізована std::optional?
idclev 463035818

2
@ Ælex немає способу перевірити, чи об’єкт ініціалізований чи ні, оскільки будь-яка операція над неініціалізованим об'єктом, крім ініціалізації, призводить до не визначеної поведінки. std::optionalОб'єкт , який містить не міститься значення по - , як і раніше є ініціалізувати об'єкт.
болов

2
Об'єкт значення ініціалізований значенням, а не ініціалізованим нулем. Для скалярних типів вони однакові, але для типів класів вони різні.
аскеплер

@bolov Я вчора спробував це протестувати за допомогою gnu 17 та std 17, і дивно, все, що у мене було, - нульова ініціалізація. Я думав, std::optional has_valueщо перевірити це не вдасться, тому я думаю, ви прав.
Ælex

Відповіді:


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аргументу від аргументів [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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.