Nerdrigo
Nerdrigo

Reputation: 328

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

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?

Upvotes: 4

Views: 4107

Answers (5)

Mark Ransom
Mark Ransom

Reputation: 308520

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.

Upvotes: 4

bweber
bweber

Reputation: 4092

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.

Upvotes: 1

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 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);

Upvotes: 1

eerorika
eerorika

Reputation: 238441

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.

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409442

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.

Upvotes: 5

Related Questions