Pointers/Memory Allocation   «Prev  Next»
Lesson 10Free Store Operators
ObjectiveUse delete and delete[] correctly; understand destructor invocation and modern RAII alternatives.

C++ delete and delete[]: Destructors, Heap, and Safer Alternatives

In C++, the delete expression releases memory previously acquired with new. For single objects, use delete p;; for arrays, use delete[] p;. Both forms invoke destructors for the object(s) and then return the storage to the free store (heap). Modern C++ favors RAII—containers (e.g., std::vector) and smart pointers (e.g., std::unique_ptr)—so you rarely write delete yourself.

Heap, constructors, and destructors: objects constructed on the free store via new, then destroyed by delete (or automatically via RAII)
The heap (free store) holds dynamically allocated objects. Construction happens via new; destruction happens via delete/delete[] (or automatically when RAII owners go out of scope).

What delete Actually Does

Correct Forms

Non-array (single object)


struct Widget {
    ~Widget() {/* release resources */}
};

Widget* p = new Widget();
delete p;            // correct
// delete[] p;      // ❌ UB (wrong form)
  

Array


Widget* arr = new Widget[10];
delete[] arr;        // correct
// delete arr;      // ❌ UB (wrong form)
  

Demonstration: Constructor/Destructor Order


#include <iostream>

struct Tracer {
    int id;
    explicit Tracer(int i) : id(i) { std::cout << "Ctor " << id << '\n'; }
    ~Tracer()                       { std::cout << "Dtor " << id << '\n'; }
};

int main() {
    Tracer* one = new Tracer(1);
    delete one; // prints: Ctor 1 ... Dtor 1

    std::size_t n = 3;
    Tracer* many = new Tracer[n]{ Tracer(10), Tracer(11), Tracer(12) };
    delete[] many; // dtors run for all elements (reverse order)
}

Safer, Modern Alternatives (RAII)

Prefer ownership types that free memory automatically—no manual delete, fewer bugs, strong exception safety.


#include <vector>
#include <memory>
#include <iostream>
#include <numeric>

int main() {
    // std::vector: resizable, RAII, bounds-checked access via .at()
    std::size_t n = 5;
    std::vector<int> v(n);
    std::iota(v.begin(), v.end(), 0);

    // unique_ptr to array: owns a raw array while keeping RAII semantics
    auto data = std::make_unique<int[]>(n);
    for (std::size_t i = 0; i < n; ++i) data[i] = static_cast<int>(i*i);

    // No delete/delete[] needed; both free themselves automatically.
}

Common Pitfalls & How to Avoid Them

Reference: Syntax Recap


// Non-array allocation
T* p = new T(args...);
delete p;

// Array allocation
T* a = new T[count];        // calls default ctor count times
delete[] a;

// Prefer
auto up  = std::make_unique<T>(args...);
auto upa = std::make_unique<T[]>(count);

Exercises

  1. Create a Tracer with logging and verify destructor order for an array via delete[].
  2. Rewrite a raw-pointer example to use std::vector, replacing manual loops with algorithms.
  3. Wrap a C API that returns a T* in a std::unique_ptr<T, Deleter> with a custom deleter.

SEMrush Software