Pitfalls with vectors

This section presents some of the common pitfalls related to the used of vectors in C++ programs. Thus, it’s important to read this section carefully to avoid misusing vectors.

Accessing inexisting elements

It’s important to keep in mind that subscripting does not add elements to a vector. Consider the following piece of code.

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <vector>

int main() {
    // Create a vector an empty vector
    std::vector<int> V1;

    V1[0] = -1; // Disaster!!
}

Vector V1 is empty, i.e. it has no elements to subscript. Thus, it’s not legal in C++ to access elements (or variables) that do not exist in the vector. Bare in mind that the statement V1[0] = -1; cannot be used to add an element (the first one) to the vector. In other words, subcripting such as V1[0] should be used to fectch the value of an existing element, and never to add new elements to the vector. The right way to add an element to the vector is to use push_back, i.e. V1.push_back(-1); adds the first value to the empty vector V1.

Another example.

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <vector>

int main() {
    // Create a vector an empty vector
    std::vector<int> V2 = {2, 6, 7, 8, 5, 4};

    std::cout << "-->" << V2[6]; // Disaster!!
}

Vector V2 has six elements, V2[0], V2[1], V2[2], V2[3], V2[4], and V2[5]. There is no element with subscript $6$. Thus, V2[6] is not legal C++, though the program compiles and might even display a value.

Subscripting an element that does not exist in a vector is an error. The compilers are unable to detect this situation. The result of subscripting an element that does not exist in a vector is undefined, i.e. it may lead to run-time errors sometimes, while other times everything may seem to work just fine.

Possible run-time error messages can be “segmentation fault” or “Illegal instruction”. Just try the examples above in, for example, Coliru (click the button “Run code” below the program) and see what you get.

And, one more example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>

int main() {
    // Create a vector an empty vector
    std::vector<int> V;

    // Add user given element to the vector
    int value;
    while (std::cin >> value) {
        V.push_back(value);
    }

   // Check whether the user has entered value -1
   int i = 0;
   while (V[i] != -1) { // Disaster!!
        ++i;
   }
}   

Imagine $-1$ is not part of the list of values entered by the user. Then, the while-loop (line $16$) will attempt to access an inexisting element, when the subscript variable i gets the value std::ssize(V) (recall that the last element of the vector has subscript std::ssize(V)-1). A possible way to write the while-loop so that it does not attempt to access elements that do not exist in the vector is given below (though, the loop could also be written in other ways).

// Check whether the user has entered value -1
int i = 0; // index of the first element in V
while (i < std::ssize(V)) { // does element V[i] exist?
    if (V[i] != -1) {
        ++i; // index of the next element in V
    }
    else {
        break; // found -1; exit the loop
    }
}

Initialization

It’s important the keep in mind that the meaning of a statement initializing a vector might depend on wether one uses curly braces or parantheses. See the examples below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>

int main() {
    std::vector<int> V1(6, -1);
	
    std::cout << "Elements in V1: ";
    for(int e: V1) {
        std::cout << e << " ";
    }

    std::vector<int> V2 = {6, -1};
    //std::vector<int> V2{6, -1};
	
    std::cout << "\nElements in V2: ";
    for(int e: V2) {
        std::cout << e << " ";
    }
}

Vector V1 is initialized (line $5$) with $6$ integer elements, all with value $-1$. Note that round brackets (‘(’ and ‘)’) have beeen used to define and initialize V1.

Vector V2 is initialized (line $12$) with $2$ integer elements, $6$ and $-1$. Note that curly brackets (‘{’ and ‘}’) have been used to define and initialize V2 (symbol = is optional).

It’s easy to confuse these two ways to define and initialize a vector.

Modifying vector size in loops

The body of a range-based loop must not modify the size of a vector over which it is iterating. This is not an error that compilers detect.

Consider the example below which traverses an initialized vector V and, for each even element, its double is then added to the end of the vector. Thus, we expect that $4$, $12$, and $16$ are appended to the vector.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>

int main() {
    std::vector<int> V = {2, 3, 6, 8};

    // For each even element e in V add 2*e to V
    for (int e : V) {
         if (e % 2 == 0) {   // is element e even?
         V.push_back(2 * e); // disaster!! 
        }
    }
    std::cout << "Elements in V: ";
    for(int e: V) {
        std::cout << e << " ";
    }	
}

The body of the range-based loop, lines $8$ to $12$ above, uses push_back (line $10$) to add new values to the vector. Consequently, the size of the vector is modified inside the loop’s body. Though the program above compiles and may execute, it is not correct and in must cases it does not work correctly. Just try it! (click the button “Run code” or copy the code into your IDE)

One could think that using a for-loop, instead of a range-based loop, would solve the problem, as show next.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>

int main() {
    std::vector<int> V = {2, 3, 6, 8};

    // For each even element e in V add 2*e to V
    for(int i = 0; i < std::ssize(V); ++i) {
        if (V[i] % 2 == 0) {       // is V[i] even?
            V.push_back(2 * V[i]); // disaster!! 
        }
    }
    std::cout << "Elements in V: ";
    for(int e: V) {
        std::cout << e << " ";
    }	
}

The solution above does not work, either. The reason is that the body of the loop, lines $8$ to $12$ above, adds new even elements to the end of the vector (line $10$). Consequently, the size of the vector increases (std::ssize(V)) and the newly added even elements will cause new elements to be added, until there’s no more memory available to increase the vector. Thus, the program above is doomed to end with a run time error. Just try it!

Errors may occur when executing a program that contains a for-loop as shown below. This type of loops are, therefore, considered a bad programming practice.

// Don't do this!!
for(int i = 0; i < std::ssize(V); ++i) {
    // Do something that modifies the vector V's size
}

The problem can be solved by storing the size of the vector in a variable before entering the loop, line $8$ of the program below, and then use this variable (old_size) in the test condition of the for-loop (line $9$).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>

int main() {
    std::vector<int> V = {2, 3, 6, 8};

    // For each even element e in V add 2*e to V
    int old_size = std::ssize(V);  // some compilers may give a warning here
    for(int i = 0; i < old_size; ++i) {
        if (V[i] % 2 == 0) {
            V.push_back(2 * V[i]);
        }
    }
    std::cout << "Elements in V: ";
    for(int e: V) {
        std::cout << e << " ";
    }	
}