8

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?

11
  • 1
    They were designed that way many years apart, so maybe the thinking changed. std::array follows "you don't pay for what you don't need" better than std::vector. Commented Feb 13, 2018 at 20:56
  • 1
    std::vector: Because of the defaulted constructor parameter see (2) in: en.cppreference.com/w/cpp/container/vector/vector "Constructs the container with count copies of elements with value value." Commented Feb 13, 2018 at 20:58
  • 2
    It would if you were to initialize the array std::array<...> arr{}; Commented Feb 13, 2018 at 21:01
  • 5
    @juanchopanza yes it does default initialised with unsigned() is default initialised with 0. If you don't want default initialisation don't give a size and call reserve to get initialised space. All the (8) constructors are consistent in that they value (or copy) initialise the vector. Commented Feb 13, 2018 at 21:03
  • 1
    @RichardCritten Not sure if you were trying to answer my comment or someone else's. What I meant is that cppreference states the mandated behaviour as per the standard. It does not state why vector was designed to do this, against one of the design philosophies of C++. Also, unsigned() is not default initialization, it is value initialization. Commented Feb 13, 2018 at 22:05

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

3 Comments

serious? I think almost ever you will use size(), and that means you need to lose the time of initializing the vector with zeros, yes or yes. right?. Because yeah push_back() give a defined behaviour but... I don't want use push_back()?
for example, how can I do something like use std::iota in a range [1, 10], without initializing the vec, and using a std or a boost function?
@MoisesRojo: Like generate_n(back_inserter(v), 10, []{ static int x = 7; return x++; }); maybe
5

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)...);
        }
    };
}

11 Comments

Yeah! Actually, I don't know why on my computer don't run, but in another, it runs perfectly.
[link] (wandbox.org/permlink/Pjkd0FlByO13k8km) Here works, but In my fedora 27, gcc 7.3 don't works
Do you have any suggestion?
Comments are not for solving programming problems.Perhaps my code is using a C++11 feature not supported by GCC 7.3. I do not use GCC or know anything about it. If you can't figure it out, post it as a new question.
Well, the allocator is not doing (or re-doing) the initialization to zeros. Just because memory contains zeros does not mean they were put there when the vector was constructed. Probably the OS clears process memory at the beginning, and your test code is using memory that has never been used since the process started. The reason the Wandbox version has zeros for 75 and up but not lower is undoubtedly that when you allocate a small amount you are getting memory that has been used before, but not so when you allocate the larger amount.
|
3

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 MyClasss, 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()};

7 Comments

I'm surprised this got selected, because it begs the question "why doesn't std::array behave like this? Why can't std::vector behave like std::array? And that question sounds pretty much like what OP is asking.
@juanchopanza That's true, but I'm not sure I want to go into that in this answer. I intended to give the OP some intuition on why std::vector behaves in this way.
Question... "Why...?" R: Because vector call the default constructor, It's implicit (or I think) that array don't call it, that solve the question
BTW @Justin Do you know how to initialize a vector n-dimensions with beginend (is begin()/end() in emacs)? like this link
@juanchopanza Thinking a bit more on it, I edited to include a short explanation for why std::array behaves the way it does. The behavior is indeed odd given only the explanation I gave before
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.