Question about vector looping

I have 2 programs, the first one:

1
2
3
4
for(int i = 0; i < a.size(); i++)
{
   a.push_back(a[i]);
}


The second one:
1
2
3
4
5
6
int n = a.size();

for(int i = 0; i < n; i++)
{
   a.push_back(a[i]);
}


The second program works, but the first one doesn't, the only difference is defining the size before looping. Can someone explain why is this happening ? Thanks.
Last edited on
a.size() gets one element larger for every pass through the loop ... So you will never catch it and i<a.size() will remain true until you run out of memory.
Thanks, I understand now. Another question is I have 2 programs, the first one:
1
2
3
4
for(int i: a)
{
    a.push_back(i);
} 


The second one:
1
2
3
4
5
6
int n = a.size();

for(int i = 0; i < n; i++)
{
   a.push_back(a[i]);
}


The second program works, but the first one doesn't. Do you understand why ? Thanks
The first program loops over all elements of vector a. But since you keep on appending elements to a, this will never stop. Specifically, the iterator will never reach the "last" element, because every time the iterator advances to the next element, one more element has been added to the vector. Therefore, this program will probably keep on adding elements to vector a until it crashes with an "out of memory" exception.

In fact, I think that modifying an std::vector will invalidate all existing iterators. So, modifying a vector while you are in the process of iterating over that same vector probably results in undefined behavior!


The second program stores the initial size of a in variable n. It then appends exactly n elements to vector a.
Last edited on
This kind of tricky logic of programs may be easier and faster to understand and resolve, if we use debugging feature of compiler, and by stepping through the code/loop etc.
Last edited on
With a std::vector, or a regular C style array, there are three ways to access the elements with a for loop.

1. The "old fashioned" method via operator[]:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

int main()
{
   std::vector v { 1, 2, 3, 4, 5 };

   for (size_t itr { 0 }; itr < v.size(); ++itr)
   {
      std::cout << v[itr] << ' ';
   }
   std::cout << '\n';
}

2. Using iterators:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

int main()
{
   std::vector v { 1, 2, 3, 4, 5 };

   for (auto itr { v.cbegin() }; itr != v.cend(); ++itr)
   {
      std::cout << *itr << ' ';
   }
   std::cout << '\n';
}

3. Using a range-base for loop:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

int main()
{
   std::vector v { 1, 2, 3, 4, 5 };

   for (const auto& itr : v)
   {
      std::cout << itr << ' ';
   }
   std::cout << '\n';
}

Doing a reverse for loop is easy with the first two type loops:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <vector>

int main()
{
   std::vector v { 1, 2, 3, 4, 5 };

   for (size_t itr { v.size() }; itr > 0; --itr)
   {
      std::cout << v[itr - 1] << ' ';
   }
   std::cout << '\n';

   for (auto itr = v.size(); itr-- > 0;)
   {
      std::cout << v[itr] << ' ';
   }
   std::cout << '\n';

   for (auto itr { v.crbegin() }; itr != v.crend(); ++itr)
   {
      std::cout << *itr << ' ';
   }
   std::cout << '\n';
}

Doing a reverse range-based for loop wasn't possible before C++20:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>
#include <ranges>

int main()
{
   std::vector v { 1, 2, 3, 4, 5 };

   // https://www.fluentcpp.com/2020/02/11/reverse-for-loops-in-cpp/
   for (const auto& itr : v | std::views::reverse)
   {
      std::cout << itr << ' ';
   }
   std::cout << '\n';
}

There are C++ containers, a std::list for example, that individual elements can't be accessed using operator[] (the container doesn't allow it) but using an iterator or range-based for loop the elements can be accessed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <list>
#include <ranges>

int main()
{
   std::list v { 1, 2, 3, 4, 5 };

   for (auto itr { v.cbegin() }; itr != v.cend(); ++itr)
   {
      std::cout << *itr << ' ';
   }
   std::cout << '\n';

   for (const auto& itr : v)
   {
      std::cout << itr << ' ';
   }
   std::cout << "\n\n";

   for (auto itr { v.crbegin() }; itr != v.crend(); ++itr)
   {
      std::cout << *itr << ' ';
   }
   std::cout << '\n';

   // https://www.fluentcpp.com/2020/02/11/reverse-for-loops-in-cpp/
   for (const auto& itr : v | std::views::reverse)
   {
      std::cout << itr << ' ';
   }
   std::cout << '\n';
}

https://en.cppreference.com/w/cpp/container

There are some caveats about using an iterator loop to "block erase" elements....erasing an element alters the iterator used as the control in the for loop:
https://en.cppreference.com/w/cpp/container/vector/erase
The container adaptors -- stack, queue and priority queue -- can't access the contained element via a for loop.
Thanks everyone I understand now, another question is what does the auto keyword mean? Should I use the auto keyword every time instead of int?
Last edited on
auto tells the compiler to deduce the type instead of requiring the programmer to explicitly declare the type. Using auto can reduce the amount of typing and the possibility of typing an incorrect type.

https://www.learncpp.com/cpp-tutorial/type-deduction-for-objects-using-the-auto-keyword/
Here's a game engine function for drawing game sprites stored in a std::vector, explicitly declaring the iterator type:
429
430
431
432
433
434
435
void GameEngine::DrawSprites(HDC hDC)
{
  // draw the sprites in the sprite vector
  std::vector<Sprite*>::iterator siSprite;
  for (siSprite = m_vSprites.begin(); siSprite != m_vSprites.end(); siSprite++)
    (*siSprite)->Draw(hDC);
}

Using auto to deduce the iterator type (the compiler is quite smart):
387
388
389
390
391
392
void GameEngine::DrawSprites(HDC hDC)
{
  // draw the sprites in the sprite vector
  for (auto siSprite = m_vSprites.begin(); siSprite != m_vSprites.end(); siSprite++)
    (*siSprite)->Draw(hDC);
}

auto in the second snippet creates the same iterator type explicitly declared in the first snippet.

I kinda prefer not typing all that iterator stuff, using auto lets the compiler do all the work.

It is all too easy to mistype that iterator stuff.

One of these days I might revisit this old code and modify the function to use a range-based for loop. That would reduce the code even more.

343
344
345
346
347
348
void GameEngine::DrawSprites(HDC hDC)
{
  // draw the sprites in the sprite vector
  for (const auto& siSprite : m_vSprites)
    siSprite.Draw(hDC);
}

Ah, the joys of using Hungarian Notation when naming variables. :\
https://en.wikipedia.org/wiki/Hungarian_notation
> Should I use the auto keyword every time instead of int?

No; if you know that the type involved would always be int, use int.

auto is useful when the precise type is either difficult to know exactly, hard to write, or may change in future. Where templates are involved, auto can be of great help.

In particular, strongly favour the clarity provided by int my_function( const std::string& )
over auto my_function( const std::string& ) or auto my_function( const auto& )
Should I use the auto keyword every time instead of int?


Hmmm. There is a difference of opinion on this. There is a school of thought that says 'Almost Always Auto' (AAA).

See Herb Sutter's blog on this:
https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
One of these days I might revisit this old code and modify the function to use a range-based for loop. That would reduce the code even more.
void GameEngine::DrawSprites(HDC hDC)
{
// draw the sprites in the sprite vector
for (const auto& siSprite : m_vSprites)
siSprite.Draw(hDC);
}


What happens when one of the pointer is nullptr ?
thmm wrote:
What happens when one of the pointer is nullptr ?

That won't happen.

The sprite manager's vector contains only valid objects. When a sprite "dies" the sprite manager will simply update its status to a new status, or deletes the sprite from the sprite manager's vector. So there is ZERO need to keep a non-functional sprite hanging around with an invalid status.

You didn't ask that question about the original code I posted, the same "is it a nullptr" issue crops up as well.

There are other sprite management functions not shown that deal with managing the sprites from birth to death.

Oh, you thought you could trick me, eh? Nope!

Oh, and another of the possible changes I'd make to this old sodden mess o' code is get away from storing pointers in a flippin' vector! A vector already knows how to use the heap, and use it quite efficiently. This old approach is just so "let's write this C++ code as if it were C!"
Last edited on
The code looks like the game engine from Michael Morrisson. I haven't used it for many years.
I wasn't sure if the sprite was removed from the vector or only deleted and set to nullptr.

Actually it would make sense to just delete it and set to nullptr, removing sth. from the beginning or middle of a vector can be expensive.

Anyway getting a reference to a nullptr is probably UB.
I can guarantee you the code doesn't set any of the vector elements to nullptr.

Over the years I've made some "special modifications" to the original pre-C++11 code to reflect modern C++ practices. Using auto and the like. Setting a pointer to nullptr was never part of the original code.

I know the code could stand some other tweaks, which I am looking at making. Smart pointers vs. raw pointers.

Anyway getting a reference to a nullptr is probably UB.

Well, DUH! Reason One why I don't set any of the vector's pointer elements to nullptr.
Thanks everyone !
Topic archived. No new replies allowed.