std::vector resizing and capacity

Unlike most array types, std::vector can change size after creation. This unique capability makes vectors incredibly flexible.

Fixed-size arrays vs dynamic arrays

Most array types require knowing the length at instantiation and cannot change afterward. These are fixed-size arrays (or fixed-length arrays). Both std::array and C-style arrays fall into this category.

std::vector is a dynamic array (or resizable array)—it can change size during program execution. This distinguishes vectors from other array types.

Resizing vectors at runtime

Use the resize() member function to change a vector's size:

#include <iostream>
#include <vector>

int main()
{
    std::vector temps { 68, 72, 75 };
    std::cout << "Size: " << temps.size() << '\n';

    temps.resize(6);  // Expand to 6 elements
    std::cout << "Size: " << temps.size() << '\n';

    for (auto temp : temps)
        std::cout << temp << ' ';

    std::cout << '\n';

    return 0;
}

Output:

Size: 3
Size: 6
68 72 75 0 0 0

Note two behaviors: existing elements preserve their values, and new elements are value-initialized (zero for built-in types).

Vectors can also shrink:

#include <iostream>
#include <vector>

int main()
{
    std::vector counts { 10, 20, 30, 40, 50 };
    std::cout << "Size: " << counts.size() << '\n';

    counts.resize(3);
    std::cout << "Size: " << counts.size() << '\n';

    for (auto count : counts)
        std::cout << count << ' ';

    std::cout << '\n';

    return 0;
}

Output:

Size: 5
Size: 3
10 20 30

Understanding length vs capacity

Think of a parking garage: you can count how many cars are currently parked (current usage) versus how many parking spots exist (total capacity). Both numbers are useful but mean different things.

Similarly, std::vector tracks both:

  • Length (or size): how many elements are currently in use
  • Capacity: how many elements the vector has allocated memory for

A vector with capacity 10 and length 4 has space for 10 elements total, with 4 currently used and 6 available for future growth without reallocation.

Key Concept
Length is how many elements are "in use". Capacity is how many elements have been allocated in memory.

Getting the capacity

Use the capacity() member function:

#include <iostream>
#include <vector>

void showInfo(const std::vector<int>& v)
{
    std::cout << "Capacity: " << v.capacity()
              << " Length: " << v.size() << '\n';
}

int main()
{
    std::vector data { 100, 200, 300 };
    showInfo(data);

    data.resize(6);
    showInfo(data);

    return 0;
}

Sample output:

Capacity: 3 Length: 3
Capacity: 6 Length: 6

Initially, capacity equals length (3). After resize(6), both increase to 6 because the vector needed more storage.

Reallocation: the expensive operation

When a vector needs more capacity, it undergoes reallocation:

  1. Allocate new memory with the required capacity
  2. Copy (or move) existing elements to new memory
  3. Deallocate old memory
  4. Update capacity and length

From outside, it looks like the vector simply grew. Internally, everything moved to a new memory location!

Reallocation is expensive because every element must be copied. Minimize unnecessary reallocations for better performance.

Key Concept
Reallocation is typically expensive. Avoid unnecessary reallocations.

Why separate length from capacity?

Separating these values lets vectors avoid constant reallocation:

#include <iostream>
#include <vector>

void showInfo(const std::vector<int>& v)
{
    std::cout << "Capacity: " << v.capacity()
              << " Length: " << v.size() << '\n';
}

int main()
{
    std::vector measurements { 45, 52, 48, 51, 47 };
    showInfo(measurements);

    measurements.resize(3);  // Shrink
    showInfo(measurements);

    measurements.resize(5);  // Grow back
    showInfo(measurements);

    return 0;
}

Output:

Capacity: 5 Length: 5
Capacity: 5 Length: 3
Capacity: 5 Length: 5

When shrinking to 3 elements, capacity remains 5—no reallocation! When growing back to 5, the existing capacity suffices—again, no reallocation! We avoided two expensive operations.

Key Concept
Tracking capacity separately from length allows vectors to avoid some reallocations when length changes.

Valid indices depend on length, not capacity

Only indices from 0 to length-1 are valid, regardless of capacity:

#include <iostream>
#include <vector>

int main()
{
    std::vector data { 10, 20, 30 };
    data.resize(2);  // Length = 2, Capacity = 3

    std::cout << data[1] << '\n';  // OK: index 1 is valid
    std::cout << data[2] << '\n';  // Undefined behavior: index 2 >= length

    return 0;
}

Even though element 2 still exists in memory, accessing it is undefined behavior.

Warning
A subscript is only valid if between 0 and the vector's length (not its capacity)!

Shrinking capacity with shrink_to_fit()

Reducing length doesn't reduce capacity automatically (to avoid unnecessary reallocation). For large vectors, this wastes memory. Use shrink_to_fit() to request capacity reduction:

#include <iostream>
#include <vector>

void showInfo(const std::vector<int>& v)
{
    std::cout << "Capacity: " << v.capacity()
              << " Length: " << v.size() << '\n';
}

int main()
{
    std::vector<int> data(2000);  // Allocate space for 2000 elements
    showInfo(data);

    data.resize(5);  // Only need 5
    showInfo(data);

    data.shrink_to_fit();  // Request capacity reduction
    showInfo(data);

    return 0;
}

Sample output:

Capacity: 2000 Length: 2000
Capacity: 2000 Length: 5
Capacity: 5 Length: 5

Note: shrink_to_fit() is a request, not a guarantee. The implementation may ignore it.

Summary

Fixed-size vs dynamic arrays: Most array types (std::array, C-style arrays) have fixed size determined at instantiation. std::vector is a dynamic array that can change size during program execution.

Length and capacity: std::vector tracks both length (how many elements are in use) and capacity (how many elements are allocated in memory). Length determines valid indices, while capacity determines when reallocation is needed.

The resize() function: Changes vector length at runtime. When expanding, new elements are value-initialized. When shrinking, excess elements are destroyed. May trigger reallocation if new size exceeds capacity.

Reallocation: When a vector needs more capacity, it allocates new memory, copies existing elements, deallocates old memory, and updates internal pointers. This is expensive, so vectors maintain extra capacity to reduce reallocation frequency.

The shrink_to_fit() function: Requests that capacity be reduced to match length, freeing unused memory. This is a non-binding request that implementations may ignore.

Capacity management: Separating length from capacity allows vectors to avoid reallocation when shrinking then re-growing. Only indices from 0 to length-1 are valid, regardless of capacity.

Understanding the distinction between length and capacity helps you write more efficient vector code and avoid subtle bugs related to invalid indices.