Pointers/Memory Allocation   «Prev  Next»
Lesson 11C++ Dynamic Arrays
ObjectiveUnderstand dynamic arrays with new[]/delete[] and modern RAII alternatives such as std::vector and std::unique_ptr.

C++ Dynamic Arrays: new[]/delete[] vs std::vector

In C++ you can obtain dynamically sized storage at run time. Historically this was done with new[] and released with delete[]. In modern C++, the preferred approach is to use RAII containers such as std::vector<T> or smart pointers (e.g., std::unique_ptr<T[]>), which free memory automatically and add safety features such as bounds-aware access and exception safety.

C++ dynamic Array
The pointer variable data acts as the base address of a dynamically allocated array with size elements. Each subscripted access like data[j] performs pointer arithmetic relative to this base address.

Raw Dynamic Allocation with new[] (for comparison)

The following minimal example shows legacy-style allocation with new[]. It compiles, but it’s easy to get wrong: you must pair new[] with delete[], use the correct type for sizes, and avoid reading uninitialized elements.


#include <iostream>
#include <vector>   // only for later contrast
#include <cassert>

int main() {
    using std::cout;
    using std::cin;

    std::size_t size{};
    cout << "Enter array size: ";
    cin  >> size;
    assert(size > 0);

    int* data = new int[size];         // allocate size ints
    // Initialize values to something meaningful before printing:
    for (std::size_t j = 0; j < size; ++j) {
        data[j] = static_cast<int>(j);
    }

    for (std::size_t j = 0; j < size; ++j) {
        cout << data[j] << '\t';
    }
    cout << '\n';

    delete[] data;                     // must use delete[] for arrays
}

What to notice

  1. Pointer as base address: data points to the first element; data[j] is computed via pointer arithmetic.
  2. Initialization matters: Printing before assigning produces indeterminate values.
  3. Exception behavior: new throws std::bad_alloc on failure (it no longer returns nullptr in standard builds). A production program should catch this.
  4. Deallocation symmetry: new[] must be paired with delete[] (not plain delete).

Modern, Safe Alternative: std::vector<int>

Prefer std::vector for resizable arrays. It owns its memory, initializes elements, supports .size(), offers bounds-checked access with .at(), and integrates with algorithms.


#include <iostream>
#include <vector>
#include <numeric>   // std::iota

int main() {
    using std::cout;
    using std::cin;

    std::size_t size{};
    cout << "Enter array size: ";
    cin  >> size;

    std::vector<int> data(size);             // RAII-managed dynamic array
    std::iota(data.begin(), data.end(), 0);  // fill with 0,1,2,...

    for (std::size_t j = 0; j < data.size(); ++j) {
        cout << data[j] << '\t';
    }
    cout << '\n';                            // automatic cleanup; no delete[] needed
}

Why vector is better

  • Automatic lifetime: No manual delete[].
  • Safety: Optional bounds checks via data.at(j) (throws on error).
  • Algorithm-friendly: Works with the STL (e.g., std::iota, std::sort).
  • Exception-safe: If an exception occurs, memory is still freed when data goes out of scope.

When You Need a Raw Array Interface

Some legacy APIs require a raw pointer. Use a smart pointer to keep RAII while honoring the API’s signature.


#include <iostream>
#include <memory>    // std::unique_ptr
#include <algorithm>

int main() {
    std::size_t size = 8;
    auto data = std::make_unique<int[]>(size);  // RAII for dynamic arrays

    for (std::size_t j = 0; j < size; ++j) data[j] = static_cast<int>(j * j);

    // Pass data.get() to functions that need int*.
    std::for_each(data.get(), data.get() + size, [](int v){ std::cout << v << '\t'; });
    std::cout << '\n';

    // No delete[] needed; unique_ptr frees memory automatically.
}

Common Pitfalls & How to Avoid Them

From C Arrays to C++ Abstractions

C-style arrays provide no bounds checking and no size tracking. C++ adds powerful abstractions that make dynamic arrays safer and more expressive:

Exercises

  1. Vector basics: Read n, create std::vector<int> of size n, fill with i, print in reverse.
  2. Bounds check: Rewrite a loop using .at() and catch the exception on an out-of-range index.
  3. Smart pointer interop: Allocate with std::make_unique<int[]>, compute prefix sums in-place, and print.

Appendix: Updated Legacy Dissection (Modernized Headers)


#include <iostream>
#include <cassert>

int main() {
    using std::cout;
    using std::cin;

    std::size_t size{};
    cout << "Enter array size: ";
    cin  >> size;
    assert(size > 0);

    int* data = new int[size];
    for (std::size_t j = 0; j < size; ++j) {
        data[j] = static_cast<int>(j);
        cout << data[j] << '\t';
    }
    cout << "\n";
    delete[] data;
}

New Storage Allocation - Exercise

Click the Exercise link below to write a function that reverses a string using storage allocated with the operator new.
New Storage Allocation - Exercise

SEMrush Software