Suppose we have some class:
class MyClass {
int value;
public:
MyClass() {
value = 42;
}
// other code
};
std::vector<MyClass> arr(10);
will default construct 10 copies of MyClass
, all with value = 42
.
But suppose it didn't default construct the 10 copies. Now if I wrote arr[0].some_function()
, there's a problem: MyClass
's constructor has not yet run, so the invariants of the class aren't set up. I might have assumed in the implementation of some_function()
that value == 42
, but since the constructor hasn't run, value
has some indeterminate value. This would be a bug.
That's why in C++, there's a concept of object lifetimes. The object doesn't exist before the constructor is called, and it ceases to exist after the destructor is called. std::vector<MyClass> arr(10);
calls the default constructor on every element so that all the objects exist.
It's important to note that std::array
is somewhat special, since it is initialized following the rules of aggregate initialization. This means that std::array<MyClass, 10> arr;
also default constructs 10 copies of MyClass
all with value = 42
. However, for non-class types such as unsigned
, the values will be indeterminate.
There is a way to avoid calling all the default constructors: std::vector::reserve
. If I were to write:
std::vector<MyClass> arr;
arr.reserve(10);
The vector would allocate its backing array to hold 10 MyClass
s, and it won't call the default constructors. But now I can't write arr[0]
or arr[5]
; those would be out-of-bounds access into arr
(arr.size()
is still 0, even though the backing array has more elements). To initialize the values, I'd have to call push_back
or emplace_back
:
arr.push_back(MyClass{});
This is often the right approach. For example, if I wanted to fill arr
with random values from std::rand
, I can use std::generate_n
along with std::back_inserter
:
std::vector<unsigned> arr;
arr.reserve(10);
std::generate_n(std::back_inserter(arr), 10, std::rand);
It's also worth noting that if I already have the values I want for arr
in a container, I can just pass the begin()/end()
in with the constructor:
std::vector<unsigned> arr{values.begin(), values.end()};