15

I would like to know the default value of variables inside a struct of static static std::unordered_map<std::string, struct>.

Here's my example code:

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
    enum MyStateType
    {
        MY_STATE_NEW,
        MY_STATE_RUN,
        MY_STATE_FREE
    };
    struct Foo
    {
        int num;
        MyStateType state;
    };
    static std::unordered_map<std::string, Foo> map;

    std::cout << map["Apple"].num << '\n';
    std::cout << map["Apple"].state << '\n';
}

The output result:

0
0
Program ended with exit code: 0

Is it safe to think that variables inside Foo are always initialized to 0 in the beginning?

Zack Lee
  • 2,784
  • 6
  • 35
  • 77
  • https://en.cppreference.com/w/cpp/language/zero_initialization – Laurent G Aug 03 '18 at 07:51
  • 6
    Don't use [`operator[]`](https://en.cppreference.com/w/cpp/container/unordered_map/operator_at) with the STL map containers. They construct the value if it is not present. Use [`.at()`](https://en.cppreference.com/w/cpp/container/unordered_map/at) instead. – Henri Menke Aug 03 '18 at 07:52
  • 5
    Yes, in this case it's safe, and in-fact mandated by the standard. New items added to both `std::map` and `std::unordered_map` via the array index `operator[]` are *value-initialized*. Barring user-defined construction (where you're responsible for member-initializing) a trivial type such as yours will most-definitely be zero-filled. – WhozCraig Aug 03 '18 at 07:53
  • 7
    @BenjaminBarrois No, that is wrong. [`operator[]`](https://en.cppreference.com/w/cpp/container/unordered_map/operator_at) will default-construct the element which in this case value-initializes the members to zero. “When the default allocator is used, this results in the key being copy constructed from key and the mapped value being value-initialized.” – Henri Menke Aug 03 '18 at 07:54
  • @HenriMenke Look at the answer here: https://stackoverflow.com/questions/6032638/default-variable-value Thinking everything is zero-initialized is in general a bad idea though this is sometimes true. This definitely can lead to different behaviors depending on what standard and compiler you are using. – Benjamin Barrois Aug 03 '18 at 07:58
  • 3
    @BenjaminBarrois I'm not saying everything will always be zero initialized but in case of [`operator[]`](https://en.cppreference.com/w/cpp/container/unordered_map/operator_at) for `std::unordered_map` the standard *guarantees* value initialization. – Henri Menke Aug 03 '18 at 08:01
  • 1
    @BenjaminBarrois that link is unrelated, and discusses a completely different situation. The OP is not asking about a naked variable declaration; he's asking about variables constructed as the tuple-second in an association container as a result of `operator[]` usage. The standard has very specific, clear rules about what happens in that case, and further places specific conditions on the object type. Ex: it must be default-constructible, for example, either user-provided or implementation-default. When the object is constructed `T()` is used. Those `()` are important. See [dcl.init]. – WhozCraig Aug 03 '18 at 08:12

1 Answers1

24

Yes, it is actually safe to assume that the values inside Foo are always initialized to zero because of the behaviour of operator[]

When the default allocator is used, this results in the key being copy/move constructed from key and the mapped value being value-initialized.

You do not provide a constructor which means that each field in Foo will be value initialized individually which for primitive types means zero initialization.

but

The problem you are actually facing here is that a field called "Apple" does not exist in your map. Unfortunately the semantics of operator[] are such that if the value does not exist, it will be created on the fly. You probably didn't even want to access a non-existent field in the map and you are asking whether it is always initialized to zero so that you can use this fact to check whether the element was there. For this purpose however, you should either use the find() or at() member function.

  • find() will return an iterator pointing to the end of the map if the element does not exist. That means you could guards the element access using

    if (auto apple = map.find("Apple"); apple != map.end()) {
        std::cout << apple->second.num << '\n';
        std::cout << apple->second.state << '\n';
    }
    

    (with the C++17 if statement initializer)

  • at() will throw an exception if the element is not found.

    std::cout << map.at("Apple").num << '\n';
    std::cout << map.at("Apple").state << '\n';
    

    This will crash your program with a std::out_of_range exception. You might feel temped to catch this exception to check whether the element existed. Don't do this. It is very bad practice to use exceptions for control flow. On top of that exception are dead slow when they are being thrown.

Henri Menke
  • 10,705
  • 1
  • 24
  • 42