Skip to content

std array Fixed-Size Array Container

std::array: Fixed-Size Array Container

std::array is a fixed-size array container that provides the safety and convenience of STL containers while maintaining the performance characteristics of C-style arrays. It's perfect for situations where you know the size at compile time and want maximum performance without dynamic allocation overhead.

It provides:

  • Fixed size: Size is part of the type and cannot change
  • Stack allocation: No dynamic memory allocation
  • Bounds checking: Safe access through at() method
  • STL compatibility: Works with all STL algorithms
  • Value semantics: Can be copied, passed to functions, returned from functions

Creating and Initializing Arrays

cpp
#include <array>
#include <iostream>

// Empty array (elements default-constructed)
std::array<int, 5> empty_array;

// Array with initial values
std::array<int, 5> numbers = {1, 2, 3, 4, 5};

// Array with partial initialization (remaining elements default-constructed)
std::array<int, 5> partial = {1, 2, 3};  // {1, 2, 3, 0, 0}

// Array from another array
std::array<int, 5> copy_array = numbers;

// Array of different types
std::array<std::string, 3> names = {"Alice", "Bob", "Charlie"};
std::array<double, 4> prices = {10.99, 20.50, 15.75, 30.25};

Basic Operations

Accessing Elements

cpp
std::array<int, 5> arr = {10, 20, 30, 40, 50};

// Random access (O(1))
int first = arr[0];           // 10
int last = arr[4];            // 50
int middle = arr[2];          // 30

// Bounds-checked access
int safe_first = arr.at(0);   // 10, throws if out of bounds
int safe_last = arr.at(4);    // 50, throws if out of bounds

// Front and back access
int front = arr.front();      // 10
int back = arr.back();        // 50

// Safe access with bounds checking
if (2 < arr.size()) {
    int safe_middle = arr[2];  // Safe because we checked
}

Iterating Over Arrays

cpp
std::array<int, 5> arr = {1, 2, 3, 4, 5};

// Range-based for loop (C++11)
for (const auto& item : arr) {
    std::cout << item << " ";  // 1 2 3 4 5
}

// Traditional index-based loop
for (size_t i = 0; i < arr.size(); ++i) {
    std::cout << arr[i] << " ";
}

// Iterator-based loop
for (auto it = arr.begin(); it != arr.end(); ++it) {
    std::cout << *it << " ";
}

// Reverse iteration
for (auto it = arr.rbegin(); it != arr.rend(); ++it) {
    std::cout << *it << " ";  // 5 4 3 2 1
}

// Using std::for_each
#include <algorithm>
std::for_each(arr.begin(), arr.end(), (int x) {
    std::cout << x << " ";
});

Performance Characteristics

Time Complexity

OperationTime ComplexityNotes
Random AccessO(1)Direct memory offset calculation
IterationO(n)Must visit all elements
Copy/AssignmentO(n)Copy all elements
Size QueryO(1)Compile-time constant

Memory Layout

cpp
std::array<int, 5> arr = {1, 2, 3, 4, 5};

// Memory layout (conceptual):
// [1] [2] [3] [4] [5]
//  ^   ^   ^   ^   ^
//  |   |   |   |   |
// arr[0] arr[1] arr[2] arr[3] arr[4]

// Elements are stored contiguously in memory
// Perfect cache locality - accessing adjacent elements is optimal
// No memory overhead beyond the data itself

Key Insight: std::array provides perfect cache locality with zero memory overhead, making it ideal for performance-critical applications.

Why Choose std::array?

Safety and Convenience

cpp
// C-style array problems
void processArray(int arr, size_t size) {
    // No way to know the actual size
    // No bounds checking
    // Can't be returned from functions
    // Decays to pointer
}

// std::array solution
void processArray(const std::array<int, 5>& arr) {
    // Size is part of the type
    // Can use arr.size() safely
    // Bounds checking with arr.at()
    // No pointer decay
    // Can be const-correct
}

// Return from functions
std::array<int, 5> createArray() {
    return {1, 2, 3, 4, 5};  // Can return arrays!
}

STL Algorithm Compatibility

cpp
#include <algorithm>
#include <numeric>

std::array<int, 5> numbers = {3, 1, 4, 1, 5};

// Sort the array
std::sort(numbers.begin(), numbers.end());  // {1, 1, 3, 4, 5}

// Find elements
auto it = std::find(numbers.begin(), numbers.end(), 4);
if (it != numbers.end()) {
    std::cout << "Found 4 at position: " << (it - numbers.begin()) << std::endl;
}

// Count occurrences
int count = std::count(numbers.begin(), numbers.end(), 1);  // 2

// Accumulate
int sum = std::accumulate(numbers.begin(), numbers.end(), 0);  // 14

// Transform
std::array<int, 5> doubled;
std::transform(numbers.begin(), numbers.end(), doubled.begin(),
              (int x) { return x * 2; });

Bounds Checking

cpp
std::array<int, 5> arr = {1, 2, 3, 4, 5};

// Safe access with bounds checking
try {
    int value = arr.at(10);  // Throws std::out_of_range
} catch (const std::out_of_range& e) {
    std::cout << "Index out of bounds: " << e.what() << std::endl;
}

// Unsafe access (undefined behavior)
// int value = arr[10];  // Undefined behavior!

// Safe bounds checking
if (10 < arr.size()) {
    int value = arr[10];  // Safe because we checked
} else {
    std::cout << "Index 10 is out of bounds" << std::endl;
}

When to Use std::array

Use std::array when:

  • Fixed size known at compile time: Size is part of the type
  • Maximum performance needed: No dynamic allocation overhead
  • Stack allocation preferred: Small arrays that fit on stack
  • Perfect cache locality required: Contiguous memory layout
  • STL algorithm compatibility needed: Want to use with algorithms

Consider alternatives when:

  • Size not known at compile time: Use std::vector
  • Dynamic sizing needed: Use std::vector
  • Very large arrays: Use std::vector to avoid stack overflow
  • Frequent size changes: Use std::vector

Summary

std::array is an excellent choice for fixed-size collections when you need: Advantages:

  • Maximum performance: No dynamic allocation overhead
  • Perfect cache locality: Contiguous memory layout
  • Stack allocation: Small arrays fit on stack
  • STL compatibility: Works with all algorithms
  • Bounds checking: Safe access through at() method
  • Value semantics: Can be copied, passed, returned

Trade-offs:

  • Fixed size: Cannot change size after creation
  • Stack allocation: Large arrays may cause stack overflow
  • Compile-time size: Size must be known at compile time

Best Use Cases:

  • Small, fixed collections: Configuration, constants, small buffers
  • Performance-critical code: Maximum speed with no overhead
  • Stack allocation preferred: Avoid heap allocation for small data
  • STL algorithm compatibility: Need to use with algorithms

Choose std::array when you know the size at compile time and want maximum performance. It's the perfect bridge between C-style arrays and STL containers, providing safety and convenience without sacrificing performance.

Questions

Q: What is the main difference between std::array and C-style arrays?

A: std::array uses less memory

std::array knows its own size and provides bounds checking through the at() method. Unlike C-style arrays, std::array is a proper container that can be passed to functions, returned from functions, and used with STL algorithms.

Q: What is the time complexity of accessing elements in std::array?

std::array provides O(1) random access to any element because it's stored in contiguous memory, just like C-style arrays. The array index is used to calculate the memory offset directly.

Q: What happens when you try to access an element beyond the bounds of std::array using at()?

std::array::at() performs bounds checking and throws std::out_of_range exception if the index is out of bounds. This provides safety compared to operatorwhich causes undefined behavior on out-of-bounds access.

Q: Can you change the size of std::array after creation?

std::array has a fixed size that cannot be changed after creation. The size is part of the type (std::array<int, 5>), making it a compile-time constant. If you need dynamic sizing, use std::vector instead.

Q: When should you prefer std::array over std::vector?

Use std::array when you know the exact size at compile time and want maximum performance. std::array has no dynamic allocation overhead, perfect cache locality, and can be allocated on the stack, making it ideal for small, fixed-size collections.