| Lesson 10 | Free Store Operators |
| Objective | Use delete and delete[] correctly; understand destructor invocation and modern RAII alternatives. |
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.
new; destruction happens via delete/delete[] (or automatically when RAII owners go out of scope).
delete Actually Doesdelete p; calls *p's destructor (if any), then deallocates its storage.delete[] p; calls the destructor for each element (in reverse construction order), then frees the block.nullptr is a no-op.new throws std::bad_alloc on failure (in standard builds), it does not return nullptr.
struct Widget {
~Widget() {/* release resources */}
};
Widget* p = new Widget();
delete p; // correct
// delete[] p; // ❌ UB (wrong form)
Widget* arr = new Widget[10];
delete[] arr; // correct
// delete arr; // ❌ UB (wrong form)
#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)
}
Prefer ownership types that free memory automatically—no manual delete, fewer bugs, strong exception safety.
std::vector<T> (default choice).std::unique_ptr<T[]>.
#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.
}
new with delete, and new[] with delete[]. Mixing them is undefined behavior.nullptr immediately after delete (or better: use RAII owners).new/delete is brittle across control paths; RAII closes those leaks.new[]; initialize before use, or use containers.
// 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);
Tracer with logging and verify destructor order for an array via delete[].std::vector, replacing manual loops with algorithms.T* in a std::unique_ptr<T, Deleter> with a custom deleter.