4

I am working on a program that uses vectors. So the first thing I did was declare my vector.

std::vector<double> x;
x.reserve(10)

(BTW, is this also considered bad practice? Should I just type std::vector<double> x(10)?)

Then I proceeded to assign values to the vector, and ask for its size.

for (int i=0; i<10; i++)
{
    x[i]=7.1;
}
std::cout<<x.size()<<std::endl;

I didn't know it would return 0, so after some searching I found out that I needed to use the push_back method instead of the index operator.

for (int i=0; i<10; i++)
{
    x.push_back(7.1);
}
std::cout<<x.size()<<std::endl;

And now it returns 10.

So what I want to know is why the index operator lets me access the value "stored" in vector x at a given index, but wont change its size. Also, why is this bad practice?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
Nerdrigo
  • 308
  • 1
  • 5
  • 14
  • 5
    It's not a bad style if you use the index operator. That's nonsense. It's bad style (or WRONG) to use reserve + index operator with no resize. The index operator is fast and doesn't have protection/safety. That's the only issue. When in doubt or debugging, use `vector::at()`, which will throw an exception if you're out of range. – The Quantum Physicist Mar 08 '17 at 15:17
  • 2
    _@aegar_ Where did you get that information? Any serious references? – πάντα ῥεῖ Mar 08 '17 at 15:20
  • 1
    If you specifically want a vector of 10 elements with the same value, you can create that in the constructor `std::vector x(10, 7.1);`. – Bo Persson Mar 08 '17 at 16:14
  • I thought it was bad style because of an answer to another question in stack overflow (C++ Vector size is returning zero). I just wanted to get clarification on it and to have a deeper understanding on how that worked. – Nerdrigo Mar 09 '17 at 09:09

5 Answers5

5

When you do x.reserve(10) you only set the capacity to ten elements, but the size is still zero.

That means then you use the index operator in your loop you will go out of bounds (since the size is zero) and you will have undefined behavior.

If you want to set the size, then use either resize or simply tell it when constructing the vector:

std::vector<double> x(10);

As for the capacity of the vector, when you set it (using e.g. reserve) then it allocates the memory needed for (in your case) ten elements. That means when you do push_back there will be no reallocations of the vector data.

If you do not change the capacity, or add elements beyond the capacity, then each push_back may cause a reallocation of the vector data.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
4

It sounds like you're asking why things are the way they are. Most of it is down to efficiency.

If x[i] were to create value if it didn't already exist, there would be two hits to efficiency. First, the caller of indexing operations should ensure the index is not beyond the current size of the vector. Second, the new element would need to be default constructed even if you're about to assign a new value into it anyway.

The reason for having both reserve and resize is similar. resize requires a default construction of every element. For something like vector<double> that doesn't seem like a big deal, but for vector<ComplicatedClass>, it could be a big deal indeed. Using reserve is an optimization, completely optional, that allows you to anticipate the final size of the vector and prevent reallocations while it grows.

push_back avoids the default construction of an element, since the contents are known, it can use a move or copy constructor.

None of this is the wrong style, use whatever's appropriate for your situation.

Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
2
std::vector<double> x;
x.reserve(10)

BTW, is this also considered bad practice?

No, creating an empty vector and reserving memory is not a bad practice.

Should I just type std::vector<double> (10)?)

If your intention is to initialize the vector of 10 elements, rather than empty one, then yes you should. (If your intention is to create an empty vector, then no)

Then I proceeded to assign values to the vector, and ask for its size.

for (int i=0; i<10; i++)
{
    x[i]=7.1;

This has undefined behaviour. Do not try to access objects that do not exist.

so after some searching I found out that I needed to use the push_back method instead of the index operator.

That is one option. Another is to use the constructor to initialize the elements: std::vector<double> (10). Yet another is to use std::vector::resize.

Why is it considered bad style to use the index operator on a vector in C++?

It is not in general. It is wrong (not just bad style) if there are no elements at the index that you try to access.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
1

Should I just type std::vector<double> x(10)?

Definitely yes!

As mentioned in @Some programmer dude's answer std::vector::reserve() only affects allocation policies but not the size of the vector.

 std::vector<double> x(10);

is actually equivalent to

 std::vector<double> x;
 x.resize(10);
Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

The bracket operator of the std::vector lets you access an item at the index i in your vector. If an item i does not exist, it cannot be accessed, neither for writing nor for reading.

So what I want to know is why the index operator lets me access the value "stored" in vector x at a given index, but wont change its size.

Because it wasn't designed to work that way. Probably the designers did not think that this behaviour would be desirable.

Please also note that std::vector::reserve does reserve memory for the vector but does not actually change its size. So after calling x.reserve(10) your vector has still got a size of 0 although internally memory for 10 elements has been allocated. If you now want to add an element, you must not use the bracket operator but std::vector::push_back instead. This function will increase the vector's size by one and then append your item. The advantage of calling reserve is that the memory for the vector must not be reallocated when calling push_back multiple times.

std::vector<double> x;
x.reserve(3);
x.push_back(3);
x.push_back(1);
x.push_back(7);

I think the behaviour you desire could be achieved using std::vector::resize. This function reserves the memory as reserve would and then actually changes the size of the vector.

std::vector<double> x;
x.resize(3);
x[0] = 3;
x[1] = 1;
x[2] = 7;

The previous code is equivalent to:

std::vector<double> x(3);
x[0] = 3;
x[1] = 1;
x[2] = 7;

Here the size is the constructor argument. Creating the vector this way performs the resize operation on creation.

bweber
  • 3,772
  • 3
  • 32
  • 57