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.