New and Delete Operators
How new/delete expressions, operator new/delete allocation functions, placement and nothrow forms, class-specific overloads, and the new-handler work in C++.
new / deletesince C++98A new-expression calls an allocation function (operator new) to obtain raw storage and then constructs an object in it, while a delete-expression runs the destructor and then calls a deallocation function (operator delete) to release the storage.
Overview
The vocabulary matters, because two distinct things share the name new:
- new-expression / delete-expression: the language constructs (
new T(args),delete p). They cannot be overloaded. They sequence allocation, construction, destruction and deallocation. operator new/operator delete: ordinary functions (the allocation and deallocation functions) that you can replace or overload. They deal only in raw bytes.
new T(args) is roughly: call operator new(sizeof(T)); construct T in the result; if the constructor throws, call the matching operator delete and rethrow. delete p is: if p is non-null, call p->~T(), then operator delete(p). Deleting a null pointer is a no-op.
Array forms (new T[n], delete[] p) use operator new[] / operator delete[], construct n elements in order and destroy them in reverse order. The array form must be paired with delete[]; mixing forms is undefined behavior.
In modern C++ (since C++11) prefer std::make_unique / std::make_shared (C++14 for make_unique) and containers over raw new. You still need to understand these mechanics for custom allocators, pools, and legacy code.
Syntax
The standard <new> header declares these replaceable global functions:
#include <new>
void* operator new(std::size_t); // C++98, throws std::bad_alloc
void* operator new(std::size_t, const std::nothrow_t&) noexcept; // C++98, returns nullptr
void* operator new(std::size_t, std::align_val_t); // C++17, over-aligned
void* operator new(std::size_t, void* ptr) noexcept; // C++98, placement (not replaceable)
void operator delete(void*) noexcept; // C++98
void operator delete(void*, std::size_t) noexcept; // C++14, sized deallocation
void operator delete(void*, std::align_val_t) noexcept; // C++17
void operator delete(void*, std::size_t, std::align_val_t) noexcept; // C++17
// operator new[] / operator delete[] mirror all of the aboveNotes on versions:
- Sized
operator deletewas added in C++14; compilers may need-fsized-deallocation(GCC/Clang enable it by default from C++14 onward on most targets). - Over-aligned allocation (types with
alignasgreater than__STDCPP_DEFAULT_NEW_ALIGNMENT__) is automatic since C++17. Before that,newof an over-aligned type was not guaranteed to honor alignment. - Since C++20, a class may declare a destroying
operator delete(T*, std::destroying_delete_t), which takes over destructor invocation as well. - Placement
operator newand its matching delete cannot be replaced.
Examples
Allocation failure and nothrow
#include <new>
#include <iostream>
int main() {
try {
auto* big = new char[~std::size_t{0} / 2]; // throws std::bad_alloc
delete[] big;
} catch (const std::bad_alloc& e) {
std::cerr << "allocation failed: " << e.what() << '\n';
}
int* p = new (std::nothrow) int[1'000'000]; // nullptr on failure
if (!p) return 1;
delete[] p;
}Placement new into an arena
Placement new constructs an object at a caller-supplied address. You are then responsible for calling the destructor explicitly; no deallocation happens.
#include <new>
#include <cstddef>
#include <string>
#include <utility>
class Arena {
alignas(std::max_align_t) unsigned char buf_[4096];
std::size_t used_ = 0;
public:
template <class T, class... Args>
T* create(Args&&... args) {
std::size_t start = (used_ + alignof(T) - 1) & ~(alignof(T) - 1);
if (start + sizeof(T) > sizeof buf_) throw std::bad_alloc{};
used_ = start + sizeof(T);
return ::new (static_cast<void*>(buf_ + start)) T(std::forward<Args>(args)...);
}
};
int main() {
Arena a;
std::string* s = a.create<std::string>("hello, arena");
s->~basic_string(); // explicit destructor; storage is released with the arena
// C++17: std::destroy_at(s) is the clearer spelling
}Use ::new so a class-specific operator new cannot hijack the call. Since C++20, std::construct_at is the constexpr-friendly alternative.
Class-specific overloads with a free list
#include <new>
#include <cstddef>
#include <cstdlib>
class Node {
struct FreeSlot { FreeSlot* next; };
static inline FreeSlot* free_ = nullptr; // C++17 inline variable
int value_[4]{};
public:
static void* operator new(std::size_t sz) {
if (sz != sizeof(Node)) return ::operator new(sz); // derived class: fall back
if (free_) { void* p = free_; free_ = free_->next; return p; }
return ::operator new(sizeof(Node));
}
static void operator delete(void* p, std::size_t sz) noexcept { // sized: C++14
if (!p) return;
if (sz != sizeof(Node)) { ::operator delete(p); return; }
auto* s = static_cast<FreeSlot*>(p);
s->next = free_;
free_ = s;
}
};The size check matters: a derived class inherits these functions, and sz will be sizeof(Derived). Give the base a virtual destructor so delete basePtr selects the right size.
Customizing out-of-memory behavior
#include <new>
#include <cstdlib>
#include <cstdio>
void on_oom() {
std::fputs("out of memory, aborting\n", stderr);
std::abort();
}
int main() {
std::set_new_handler(on_oom); // C++98; get_new_handler is C++11
// ...
}A global replacement operator new loops: attempt allocation, and on failure call the current new-handler and retry. The handler must free memory, install a different handler, throw bad_alloc (or derived), or terminate.
void* operator new(std::size_t n) { // replaces global version
for (;;) {
if (void* p = std::malloc(n ? n : 1)) return p;
if (auto h = std::get_new_handler()) h(); // C++11
else throw std::bad_alloc{};
}
}
void operator delete(void* p) noexcept { std::free(p); }
void operator delete(void* p, std::size_t) noexcept { std::free(p); } // C++14Best Practices
- Prefer RAII. Use
std::unique_ptr,std::make_shared,std::vector, orstd::pmrcontainers (C++17) instead of nakednew. - Replace in pairs. Whenever you replace or overload
operator new, supply the matchingoperator delete(including sized and aligned variants you might hit). A mismatch between your allocator andfree/default delete is undefined behavior. - Prefer class-specific overloads over replacing the global ones, unless you are intentionally instrumenting the whole program.
- Provide all three forms (plain, nothrow, placement) in a class that declares any
operator new, or addusing ::operator new;, because a class-scope declaration hides the global overloads. - Pair placement-new with a matching placement-delete. If a constructor throws in
new (args) T, the runtime callsoperator delete(void*, args...). If none exists, nothing is called and the storage leaks. - Keep deallocation functions
noexceptand never let exceptions escape them or destructors. - Use
::newin generic code that constructs into raw storage.
Common Pitfalls
deletevsdelete[]mismatch.new T[n]requiresdelete[]. Usingdeleteis undefined behavior, and may destroy only one element or corrupt the heap. Array allocation may store a hidden element count, so the pointer fromoperator new[]can differ from the one returned to you.- Treating
operator newas a constructor. It returns raw bytes; no object exists until construction. Casting the result and reading from it is UB. - Forgetting the new-handler isn't called by custom allocators. If you write
operator newwithmalloc, you must invoke the handler yourself (see above) and throwbad_allocon failure; returningnullptrfrom a throwingoperator newis not permitted. - Double delete / use after delete. Set owning raw pointers to
nullptronly as a stopgap; the real fix is smart pointers. - Leaks from partially-constructed arrays in hand-rolled containers. When writing
new T[n]replacements with placement new, destroy already-built elements if a later constructor throws. - Mixing allocators. Memory from
mallocmust go tofree, memory fromnewtodelete. Never cross them. - Over-aligned types before C++17.
newdid not reliably honor extended alignment; usestd::aligned_allocor a custom allocator, or move to C++17. - Unsized
deleteon polymorphic types without a virtual destructor. Deleting a derived object through a base pointer is undefined behavior, and a sizedoperator deletereceives the wrong size. newexpression evaluation order. Before C++17,f(std::unique_ptr<T>(new T), g())could leak ifg()threw between allocation and ownership. C++17 tightened sequencing, butmake_uniqueremains the safest idiom.
See Also
- alignas: requesting extended alignment, which drives the aligned
operator newoverloads in C++17. - Aggregate initialization: initializing objects created by
new T{...}. - Argument-dependent lookup: how non-member operator overloads are found.