Simegnew Alaba
13 min readJul 27, 2024

Arrays vs. Vectors in Modern C++: A Detailed Comparison with Examples

Introduction

In C++ programming, mainly modern C++ (C++11 and later), efficient data management is crucial for developing high-performance applications. Three fundamental data structures in C++ are arrays,std::array, and vectors. Each of these has its own characteristics and use cases. Understanding the differences between them is essential for making informed decisions in your code.

Arrays in C++

Definition and Basic Usage

An array is a collection of elements of the same type stored in contiguous memory locations. Its size is fixed and determined at compile time. Arrays provide fast access to elements via indices.

Declaration and Initialization

General declaration of arrays is as follows:

type name[NumElements];

Type: Can be any primitive data type (e.g., int, char, float) or a user-defined type (e.g., struct, class).

Name: The identifier for the array. C++ treats the array name (identifier) as the memory location of the first array element.

NumElements: The number of elements the array will hold.

The initialization of int myArray[5]{1, 2, 3, 4, 5} is a modern C++ style that is equivalent to the C-style initialization (int myArray[5] = {1, 2, 3, 4, 5}). When initializing an array, you can omit the size if you provide an initializer list. The compiler deduces the size of the array from the number of elements in the initializer list. For example, int myArray[]{1, 2, 3, 4, 5} has five elements. However, do not forget to use empty brackets to indicate it is an array. Similarly, initializing with empty braces sets all elements to zero or the default value for the type. For example, in int myArray[5]{}, all five elements are initialized to zero.

int myArray[5];                    // Declaration of an array of 5 integers
int myArray[5]{1, 2, 3, 4, 5}; // Declaration and initialization
int myArray[]{1, 2, 3, 4, 5}; // Compiler deduces the size of the array
int myArray[5]{}; // All elements initialized to zero

Why do we need arrays?

Using arrays in C++ is essential when dealing with large datasets or repetitive tasks. When working with a small amount of data, you can manage it using individual variables, but as the data size grows (e.g., 100, 1000 elements), arrays are crucial for simplifying your code and making it more efficient.

Case 1: Small Data Using Individual Variables

If you have to store and process just a few values (for example, 3 test scores), you can handle it with individual variables.

#include <iostream>

int main() {
// Storing 3 test scores
int score1 = 85;
int score2 = 90;
int score3 = 78;

// Calculating the total and average
int total = score1 + score2 + score3;
float average = total / 3.0;

std::cout << "Total: " << total << std::endl;
std::cout << "Average: " << average << std::endl;

return 0;
}

Using individual variables works fine in this case because we only handle three values. You can easily calculate the total and average.

Case 2: Handling Large Data Without Arrays (Impractical)

Now, imagine you have to store 1000 test scores. Without arrays, you would need 1000 individual variables, making the code long, repetitive, and error-prone.

#include <iostream>

int main() {
// Storing 1000 test scores (this is impractical and cumbersome)
int score1 = 85, score2 = 90, score3 = 78, /*...*/, score1000 = 88;

// Calculating the total (doing this manually for 1000 variables would be a huge task)
int total = score1 + score2 + score3 + /*...*/ + score1000;

float average = total / 1000.0;

std::cout << "Total: " << total << std::endl;
std::cout << "Average: " << average << std::endl;

return 0;
}

In this case, handling large datasets without arrays is highly inefficient because:

  • You would need to declare 100 variables.
  • You would have to sum up all the variables manually, which is cumbersome.
  • Updating or processing these variables would require a lot of repetitive code.

Case 3: Using Arrays for large data in C++ (Efficient and Practical)

Using an array simplifies and optimizes handling large datasets. You can store multiple values in a single array and easily perform operations on them using loops.

#include <iostream>

int main() {
// Create an array to store 1000 test scores
int scores[1000] = {85, 90, 78, 88, 92, /*...*/ , 88}; // Storing 1000 test scores

// Calculate the total using a loop
int total = 0;
for (int i = 0; i < 1000; i++) {
total += scores[i];
}

// Calculate the average
float average = total / 1000.0;

std::cout << "Total: " << total << std::endl;
std::cout << "Average: " << average << std::endl;

return 0;
}

The array makes the code scalable and efficient. Even if you had 10000 or 100,000 scores, the code structure remains the same — just the size of the array and the loop iteration would change. That’s why we need arrays and vectors.

Accessing Elements

Elements in an array can be accessed using the index operator [].

int firstElement = myArray[0];     // Access the first element
myArray[2] = 10; // Modify the third element

Advantages and Limitations

Advantages:

√ Fast access time due to direct indexing.

√ Low memory overhead.

Limitations:

√ Fixed size, which cannot be changed after declaration.

√ No built-in methods for easy insertion and deletion of elements.

std::array in C++

Definition and Basic Usage

std::array is a container that encapsulates fixed-size arrays. It provides the benefits of arrays while offering additional safety and functionality. Unlike traditional arrays, std::array is a part of the C++ Standard Library and is templated to enforce type safety.

General Declaration and Initialization

std::array<type, NumElements> name;  
  • type: The data type of the elements.
  • NumElements: The number of elements in the array.
  • name: The identifier for the std::array.

Example:

#include <array>

std::array<int, 5> myArray; // Declaration of a std::array of 5 integers
std::array<int, 5> myArray = {1, 2, 3, 4, 5}; // Declaration and initialization

Accessing Elements

Elements in a std::array can be accessed using the index operator [] or the at() method.

int firstElement = myArray[0];       // Access the first element
myArray[2] = 10; // Modify the third element
int secondElement = myArray.at(1); // Access the second element with bounds checking
  • Using indices ([]) provides fast, direct access to elements but does not perform bounds checking, similar to traditional arrays.
  • Using at() provides access to elements with bounds checking, throwing an exception if the index is invalid.

Advantages and Limitations

Advantages:

  • Fixed size, ensuring no dynamic allocation.
  • Provides better safety with bounds checking via at().
  • Works seamlessly with C++ Standard Library algorithms and functions.
  • Slightly more overhead than raw arrays but safer and more versatile.

Limitations:

  • Still lacks dynamic resizing capabilities.
  • Slightly less efficient in terms of memory usage compared to raw arrays due to added safety features.

Vectors in C++

Definition and Basic Usage

A vector is a dynamic array provided by the Standard Template Library (STL) in C++. It can resize itself automatically when added or removed elements, making it more flexible than traditional arrays.

Declaration and Initialization

Vector should be included before using it as a preprocessor directive (#include <vector>). Then, it can be declared as std::vector<type> name;. Additionally, vectors can be declared with a specified number of elements or with both the number of elements and a value.

std::vector<type> name;                  // General form for an empty vector
std::vector<type> name(NumElements); // Vector of size NumElements
std::vector<type> name(NumElements, value); // Vector of size NumElements, all initialized to value
std::vector<type> name{initializer_list}; // Vector initialized with values

Example:

#include <vector>
std::vector<int> myVector; // Declaration of an empty vector
std::vector<int> myVector(5); // Vector of size 5 with default-initialized elements
std::vector<int> myVector(5, 0); // Vector of size 5 with all elements initialized to 0
std::vector<int> myVector{1, 2, 3, 4, 5}; // Declaration and initialization

Accessing Vector Elements

Like arrays, elements in a vector can be accessed using the index operator [] and the at() method.

int firstElement = myVector[0];           // Access the first element
myVector[2] = 10; // Modify the third element
int secondElement = myVector.at(1); // Access the second element using at()
myVector.at(3) = 20; // Modify the fourth element using at()

Using indices ([]) provides fast, direct access to elements but does not perform bounds checking, which can lead to undefined behavior if an invalid index is used.

Using at() provides access to elements with bounds checking, throwing an out_of_range exception if the index is invalid, making it safer than using indices directly.

Adding Elements with ‘push_back

The push_back method adds an element to the end of the vector.

myVector.push_back(6); // Add an element to the end of the vector

In C++, multidimensional arrays (also known as arrays of arrays or matrices) can be created using vectors of vectors. Vectors can also be used to create multidimensional data structures where each element is itself a vector.

Why Use Vectors of Vectors?

When working with 2D or higher-dimensional data, such as matrices, grids, or tables, it is often convenient to represent this data as a vector of vectors. This allows for more dynamic memory allocation than arrays, and vectors provide built-in functionalities like resizing.

A vector of vectors can be declared as:

std::vector<std::vector<int>> matrix;//for int type
//This creates a dynamic 2D array (matrix) where each element of
// the main vector is itself a vector of integers.
#include <iostream>
#include <vector>
using namespace std;

int main() {
// Create a 2D vector (array) using initializer lists for each row
vector<vector<int>> myVector= {
{18, 2, 3, 4}, // Row 1
{5, 6, 7, 8}, // Row 2
{9, 10, 11, 12}, // Row 3
{13, 14, 15, 16} // Row 4
};

// Print the 2D array (myVector) with its values
cout << "2D Array (myVector) using initializer lists:" << endl;
for (int i = 0; i < myVector.size(); i++) {
for (int j = 0; j < myVector[i].size(); j++) {
cout << myVector[i][j] << " ";
}
cout << endl;
}

return 0;
}

Output:

2D Array (myVector) using initializer lists:
18 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

You can also add rows dynamically to the vector of vectors using push_back() along with initializer lists. Here’s an example:

#include <iostream>
#include <vector>
using namespace std;

int main() {
// Create an empty 2D vector
vector<vector<int>> myVector;

// Dynamically add rows to the 2D vector using initializer lists
myVector.push_back({18, 2, 3, 4}); // Adding first row
myVector.push_back({5, 6, 7, 8}); // Adding second row
myVector.push_back({9, 10, 11, 12}); // Adding third row
myVector.push_back({13, 14, 15, 16}); // Adding fourth row

// Print the dynamically added 2D array (matrix)
cout << "Dynamically added 2D Array:" << endl;
for (int i = 0; i < myVector.size(); i++) {
for (int j = 0; j < myVector[i].size(); j++) {
cout << myVector[i][j] << " ";
}
cout << endl;
}

return 0;
}

Vectors of vectors allow you to create jagged arrays, where each row can have a different number of elements. This is something that is not possible with fixed-size 2D arrays in C++.

#include <iostream>
#include <vector>
using namespace std;

int main() {
// Create a jagged 2D array (vector of vectors) where rows have different sizes
vector<vector<int>> jaggedArray = {
{18, 2}, // Row 1 (2 elements)
{5, 6, 7}, // Row 2 (3 elements)
{9, 10, 11, 12}, // Row 3 (4 elements)
{13} // Row 4 (1 element)
};

// Print the jagged 2D array
cout << "Jagged 2D Array:" << endl;
for (int i = 0; i < jaggedArray.size(); i++) {
for (int j = 0; j < jaggedArray[i].size(); j++) {
cout << jaggedArray[i][j] << " ";
}
cout << endl;
}

return 0;
}

Output:

Jagged 2D Array:
18 2
5 6 7
9 10 11 12
13

Jagged Array refers to each row in this 2D Array with a different number of elements.

This is one of the main advantages of using vectors of vectors over traditional arrays: they allow for dynamic sizes and structures.

Therefore, using vectors of vectors in C++ allows you to create multi-dimensional arrays that can be easily manipulated dynamically.

Advantages and Limitations

Advantages:

√ Dynamic resizing allows for flexible data management.

√ Provides various built-in methods like push_back, pop_back, and size (Check the examples in the Looping Through Elements section).

√ Safer to use with bounds checking available via at().

Limitations:

√ Slightly slower than arrays due to dynamic nature and additional memory management.

√ Slightly higher memory overhead due to maintaining capacity and size.

Detailed Comparison

Memory Management

√ Arrays use static memory allocation. The size of an array must be known at compile time and cannot be changed.

√ std::array: Also uses static memory allocation with the added benefit of being part of the C++ Standard Library, providing better integration with other C++ features.

√ Vectors use dynamic memory allocation, allowing them to grow or shrink at runtime as needed. This involves reallocating memory, which can incur a performance penalty.

Performance

√ Arrays provide constant time (O(1)) access to elements and are generally faster due to the lack of dynamic memory management.

std::array: Provide similar performance to raw arrays but with the added benefits of safety and usability within the C++ Standard Library.

√ Vectors also provide constant time access, but insertion and deletion of elements (especially in the middle) can be slower due to potential reallocation and shifting of elements.

Flexibility

Arrays are fixed in size, making them less flexible for scenarios where the number of elements can change.

std::array: Also fixed in size but offers better integration with the C++ Standard Library.

Vectors offer more flexibility with their dynamic resizing capabilities. This means they can adjust their size as elements are added or removed, making them suitable for most situations where the size of the data set is not known in advance.

Looping Through Elements

Example 1: Using an Array

Traditional For Loop

The traditional for loop is a versatile looping construct that allows precise control over the iteration process.

The loop initializes i to 0, checks if i is less than 5, and increments i after each iteration. Provides control over the iteration process, allowing operations on the index directly.

#include <iostream>
int main() {
int myArray[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
return 0;
}

We can also loop elements using Range-Based for Loop. The range-based for loop is a more concise and readable way to iterate over elements of an array or vector.

#include <iostream>
int main() {
int myArray[5]{1, 2, 3, 4, 5};
for (auto element: myArray) {//auto can be replaced with int here.
std::cout << element<< " ";
}
return 0;
}

Advantages of Range-Based for Loop

√ Readability: More concise and easier to read, reducing the risk of off-by-one errors.

√ Simplicity: Automatically handles the beginning and end of the container, reducing boilerplate code.

√ Safety: Reduces the likelihood of errors associated with indexing.

Example 2: Using std::array

It works similarly to arrays except that with std::array, you can use the size() method to obtain the number of elements similar to a vector, which is safer and clearer than hardcoding the size.

#include <iostream>
#include <array>

int main() {
std::array<int, 5> myArray = {1, 2, 3, 4, 5};

for (int i = 0; i < myArray.size(); ++i) {
std::cout << myArray[i] << " ";
}
return 0;
}

Range-based for loop

#include <iostream>
#include <array>

int main() {
std::array<int, 5> myArray = {1, 2, 3, 4, 5};

for (int elem : myArray) {
std::cout << elem << " ";
}
return 0;
}

Example 3: Using a Vector

A range-based loop works for vectors, too. Additionally, the push_back method adds elements to the vector’s end, demonstrating the vectors’ dynamic resizing capability.

#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector{1, 2, 3, 4, 5};
//Traditional for loop
for (int i = 0; i < myVector.size();i++){
std::cout<< myVector[i] << " ";
}
std::cout<< std::endl;
// Range-based for loop with value
for (int elem: myVector) {
std::cout << elem << " ";
}
std::cout << std::endl;
// Range-based for loop with reference
for (int& elem: myVector) {
elem *= 2; // Modifying the elements
}
for (int elem: myVector) {
std::cout << elem << " "; // Printing modified elements
}
std::cout<<std::endl;
// Adding elements with push_back
myVector.push_back(6); // Add an element to the end of the vector
myVector.push_back(7);
for (int elem: myVector) {
std::cout <<elem << " "; // Printing the vector elements after push_back
}
return 0;
}
Output
1 2 3 4 5
1 2 3 4 5
2 4 6 8 10
2 4 6 8 10 6 7 %

Explaining the ampersand (&) is not the focus of this writing. However, to provide a general understanding: without the ampersand (&), the loop iterates over a copy of each element, so modifications to elem do not affect the original array. In contrast, with the ampersand (&), the loop iterates over references to the elements, allowing modifications to elem to affect the original array.

For easier and more manageable programming, it is better to use the namespace std at the beginning of the code rather than using std:: every time, such as std::cout, std::cin, and std::endl.

#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> myVector{1, 2, 3, 4, 5};
//Traditional for loop
for (int i = 0; i < myVector.size();i++){
cout<< myVector[i] << " ";
}
cout<< endl;
// Range-based for loop with value
for (int elem: myVector) {
cout << elem << " ";
}
cout << endl;
// Range-based for loop with reference
for (int& elem: myVector) {
elem *= 2; // Modifying the elements
}
for (int elem: myVector) {
cout << elem << " "; // Printing modified elements
}
cout<<endl;
// Adding elements with push_back
myVector.push_back(6); // Add an element to the end of the vector
myVector.push_back(7);
for (int elem: myVector) {
cout <<elem << " "; // Printing the vector elements after push_back
}
return 0;
}
Output
1 2 3 4 5
1 2 3 4 5
2 4 6 8 10
2 4 6 8 10 6 7 %

Note: It is better to use g++ -std=c++11 filename.cpp -o executablename while debugging and executing the code from the terminal.

When to Use Arrays vs. Vectors

√ Arrays: Best used when the number of elements is fixed and known at compile time, such as in embedded systems or when performance is critical.

√ Vectors: Ideal for situations where the number of elements can vary, or when you need the flexibility of dynamic resizing and additional functionality provided by the STL.

Conclusion

Arrays and vectors are essential tools in a C++ programmer’s toolkit, each with strengths and weaknesses. While arrays offer low overhead and direct memory management, std::array adds the benefits of safety and better integration with the C++ Standard Library. Vectors provide dynamic sizing and ease of use but come with a slight performance cost. Choosing between them depends on your application’s specific requirements, such as the need for fixed size versus dynamic resizing and performance considerations.

Follow me for more on computer vision, deep learning, autonomous driving, and deploying deep learning models to edge devices like NVIDIA Jetson.

Simegnew Alaba

Simegnew Alaba is a postdoctoral research associate specializing in computer vision, deep learning, and autonomous driving.