Isn't it a waste of time to initialize a vector with zeros, when you don't want it?
I try this code:
#include <iostream>
#include <vector>
#include <array>
#define SIZE 10
int main()
{
#ifdef VECTOR
std::vector<unsigned> arr(SIZE);
#else
std::array<unsigned, SIZE> arr;
#endif // VECTOR
for (unsigned n : arr)
printf("%i ", n);
printf("\n");
return 0;
}
and I get the output:
with vector
$ g++ -std=c++11 -D VECTOR test.cpp -o test && ./test
0 0 0 0 0 0 0 0 0 0
with an array
g++ -std=c++11 test.cpp -o test && ./test
-129655920 32766 4196167 0 2 0 4196349 0 1136 0
And I also try with clang++
So why zeros? And by the way, could I declare a vector without initializing it?
The more common way to declare a vector is without specifying the size:
std::vector<unsigned> arr;
This doesn't allocate any space for the vector contents, and doesn't have any initialization overhead. Elements are usually added dynamically with methods like .push_back()
. If you want to allocate memory you can use reserve()
:
arr.reserve(SIZE);
This doesn't initialize the added elements, they're not included in the size()
of the vector, and trying to read them is undefined behavior. Compare this with
arr.resize(SIZE);
which grows the vector and initializes all the new elements.
std::array
, on the other hand, always allocates the memory. It implements most of the same behaviors as C-style arrays, except for the automatic decay to a pointer. This includes not initializing the elements by default.
The default allocator is doing the zero-initialization. You can use a different allocator that does not do that. I wrote an allocator that uses default construction rather than initialization when feasible. More precisely, it is an allocator-wrapper called ctor_allocator
. Then I define a vector
template.
dj:vector<unsigned> vec(10);
does exactly what you want. It's an std::vector<unsigned> (10)
that is not initialized to zeros.
--- libdj/vector.h ----
#include <libdj/allocator.h>
#include <vector>
namespace dj {
template<class T>
using vector = std::vector<T, dj::ctor_allocator<T>>;
}
--- libdj/allocator.h ----
#include <memory>
namespace dj {
template <typename T, typename A = std::allocator<T>>
class ctor_allocator : public A
{
using a_t = std::allocator_traits<A>;
public:
using A::A; // Inherit constructors from A
template <typename U> struct rebind
{
using other =
ctor_allocator
< U, typename a_t::template rebind_alloc<U> >;
};
template <typename U>
void construct(U* ptr)
noexcept(std::is_nothrow_default_constructible<U>::value)
{
::new(static_cast<void*>(ptr)) U;
}
template <typename U, typename...Args>
void construct(U* ptr, Args&&... args)
{
a_t::construct(static_cast<A&>(*this),
ptr, std::forward<Args>(args)...);
}
};
}
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()};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.