Arrays

Arrays are the simplest C++ container for storing ordered data of a uniform type.

Two types of array are available:

  • Basic arrays as defined in the C programming language.
  • The enhanced std::array from the C++ Standard Library.

💡 Best Practice:

Unless your collection size is fixed, std::vector should be preferred over arrays.

💡 Best Practice:

With Unreal Engine use the TArray container class in place of arrays and vectors.

Table of Contents

  1. C-Style Arrays
  2. C-Style Arrays Definition
  3. C-Style Array Set and Get
  4. C-Style Array Length
  5. Copying C-Style Arrays
  6. The C++ Standard Array
  7. Defining Standard Arrays
  8. Standard Array Length
  9. C-Style vs Standard Array
  10. Out-of-Bounds Behaviour
  11. Looping Over Standard Arrays
  12. Copying Arrays
  13. Standard Arrays as Function Arguments
  14. Passing Arrays by Reference
  15. Vector > Standard Array > C-Style Arrays
  16. Further Reading

C-Style Arrays

As the name implies, C-Style Arrays are arrays as defined in the C programming language.

They are only presented here because you may run into them in legacy C++ code.

💡 Best Practice:

Avoid using C-Style Arrays in C++.

C-Style Arrays Definition

C-Style Arrays are defined with a type and a length.

int primes[4]{}; // An array of four ints.

Arrays can be initialized when declared:

int primes[4]{ 2, 3, 5, 7 };

🎵 Note:

Array length cannot be changed once defined.

C-Style Array Set and Get

We can store and retrieve data into the array positions using zero-base indexes and square braces:

int primes[4]{}; // An array of four ints.

primes[0] = 2; // First element has index 0
primes[1] = 3;
primes[2] = 5;
primes[3] = 7; // Last element has index 3 (length-1)

// Retrieve array elements by position:
int sum = primes[0] + primes[1] + primes[2] + primes[3]

C-Style Array Length

The length of an array can be retrieve using std::size() from the <iterator> header:

#include <iterator>
#include <iostream>

int main() {
  int fibonacci[7]{ 1, 1, 2, 3, 5, 8, 13 };
  int length = std::size(fibonacci);
  std::cout << "I have the first " << length << " Fibonacci numbers.";
}

âš¡ Warning:

The std::size() function won’t work on arrays passed as arguments to functions.

Copying C-Style Arrays

C-Style arrays are not copied by using the assignment operator:

double copy[4]{ 1, 2, 3, 4 };
double pasta[4];

// This will not make a copy:
pasta = copy; // COMPILER ERROR: Array type is not assignable.

Instead we need to use std::copy from the <algorithm> header:

std::copy(std::begin(copy), std::end(copy), std::begin(pasta));

The C++ Standard Array

std::array was designed as a zero-overhead wrapper for C-Style Arrays.

Standard Array includes the following enhancements over C-Style Arrays:

  • Easily assigned and copied.
  • Length can be determined when passed into a function.
  • The length and type can be inferred when initialized during declaration.

💡 Best Practice:

Always prefer std::array over C-Style arrays. Lots to gain, little to lose.

Defining Standard Arrays

To use a standard array we must first include the correct header:

#include <array>

Standard arrays are defined with a type and a size:

// Array of five integers
std::array<int, 5> evenNumbers;

The type and size can be inferred if an initializer list is provided:

std::array evenNumbers{ 2, 4, 6, 8, 10 };

🎵 Note:

Uninitialized array positions default to a value of zero.

Standard Array Length

A standard array can be queried for its own length using the size() method.

std::array evenNumbers{ 2, 4, 6, 8, 10 };
int length = evenNumbers.size();

C-Style vs Standard Array

Here’s a program that demonstrates some of the differences between std::array and C-Style Arrays.

#include <iostream>  // For std::cout
#include <algorithm> // For std::copy
#include <array>     // For std::array

int main() {
  // Two C-Style Arrays (Length: 4, Type: int) 
  double original[4]{ 2, 4, 6, 8 };
  double duplicate[4];

  // This doesn't work for a copy:
  duplicate = original; // COMPILER ERROR!

  // Instead we must manually copy from original to duplicate array:
  std::copy(std::begin(original), 
            std::end(original), 
            std::begin(duplicate));

  // Must use a separate function to get array length:
  std::cout << "Length of array: " << std::size(duplicate) << "\n";

  // Change position three of the original:
  original[3] = 99;

  // Nice! The copy still has 8 in position 3:
  std::cout << "Still 8: " << duplicate[3] << "\n";

  // Two enhanced Standard Arrays (Length:4, Type: int)
  std::array stdOriginal{ 2, 4, 6, 8 };  // Type and length inferred.
  std::array<int,4> stdDuplicate;   // Type and length specified.

  // Copy by assignment. Easy!
  stdDuplicate = stdOriginal; 

  // Standard arrays can be queries for their length:
  std::cout << "Length of array: " << stdDuplicate.size() << "\n";

  // Original and copy are separate instances:
  stdOriginal[3] = 99;
  std::cout << "Still 8: " << stdDuplicate[3] << "\n";
}

Out-of-Bounds Behaviour

Both C-Style and Standard Arrays are missing array boundary checking. Historically this has been (and continues to be) a major source of bugs and security exploits.

#include <iostream>
#include <array>

int main() {
  std::array numbers{ 1, 2, 3, 4, 5 }; 
  
  // Will this work?
  std::cout << "numbers[99] is " << numbers[99] << "\n";
  numbers[99] = 42;
  std::cout << "numbers[99] is " << numbers[99] << "\n";

  // How about if we go further outside the bounds?
  std::cout << "numbers[9999] is " << numbers[9999] << "\n";
  numbers[999] = 42;
  std::cout << "numbers[9999] is " << numbers[9999] << "\n";
}

💡 Best Practice:

Manually include guards in your code to prevent out-of-bounds reads or writes.

Looping Over Standard Arrays

The two simplest ways to loop over standard arrays:

#include <iostream> // For std::cout
#include <array>    // For std::array

int main() {
  std::array fibonacci{ 1, 1, 2, 3, 5, 8, 13 };
  
  // Regular indexed for loop:
  for (auto i = 0; i < fibonacci.size(); ++i) {
    std::cout << fibonacci[i] << "\n";
  }

  // Range based for loop:
  for (auto number : fibonacci) {
    std::cout << number << "\n";
  }
}

🎵 Note:

The loop variable i will be of type std::size_t. More on this type.

Copying Arrays

An easier way to copy arrays is to use the array copy constructor:

  std::array arrayOne{ 3, 1, 4, 1, 5, 9};
  std::array arrayTwo{ arrayOne }; // Copy arrayOne into arrayTwo.

Standard Arrays as Function Arguments

Standard Arrays can be received by function parameters.

Unlike C-Style Arrays, Standard Arrays don’t lose their length information once inside the function.

double calculateAverage(std::array<double, 5> data) {
  double sum{0.0};
  for (auto number : data) {
    sum += data;
  }
  return sum / data.size();
}

🎵 Note:

When passing a Standard Array to a function, a copy of the array will be made.

Passing Arrays by Reference

C++ functions are pass-by-value by default, meaning copies are made of the arguments passed to a function.

We can switch to pass-by-reference with the & operator. Passing a reference to an argument avoids the performance hit of the copy.

// Array passed as a reference for performance and as a const for safety:
double calculateAverage(const std::array<double, 5>& data) {
  // Identical function body as above.
}

💡 Best Practice:

For safety, we mark reference arguments as immutable using const.

Without the const we might accidentally change the array while in the function, affecting the referenced array outside of the function.

Vector > Standard Array > C-Style Arrays

Above we stated that you should prefer Standard Arrays over C-Style Arrays.

C++ also has a collection type called a Standard Vector, which is similar to a std::array, but without a fixed length. We’ll cover vectors in the next section.

💡 Best Practice:

Unless your collection length is fixed, prefer std::vector over std::array.

Further Reading