How C++ Nothrow Operator New Handles Memory Failure
In C++, dynamic memory allocation using the standard new
operator throws a std::bad_alloc exception when the system
cannot allocate the requested memory. The std::nothrow
version of operator new changes this default behavior by
suppressing the allocation exception and returning a null pointer
instead. This article explains how the nothrow mechanism
works internally during allocation failures, its interaction with object
constructors, and how to use it safely in modern C++ code.
The Standard vs. Nothrow Allocation
By default, an allocation failure in C++ triggers an exception:
try {
int* data = new int[1000000000000L]; // Throws std::bad_alloc on failure
} catch (const std::bad_alloc& e) {
// Handle allocation failure
}When importing the <new> header, developers can
pass the std::nothrow constant to the new
expression. This tag directs the compiler to use an alternative overload
of the allocation function:
#include <new>
int* data = new (std::nothrow) int[1000000000000L];
if (!data) {
// Allocation failed, pointer is nullptr
}The Mechanism Behind std::nothrow
The identifier std::nothrow is a constant object of type
std::nothrow_t. Its sole purpose is overload resolution,
allowing the compiler to select the non-throwing overload signature:
void* operator new(std::size_t count, const std::nothrow_t&) noexcept;
void* operator new[](std::size_t count, const std::nothrow_t&) noexcept;When this overload executes:
- Memory Request: The function attempts to allocate
the requested number of bytes via the underlying system allocator (such
as
mallocor an OS-level heap API). - New-Handler Invocation: If the system allocator
returns a null pointer, the runtime checks if a custom
std::new_handlerhas been registered. If present, the handler is invoked repeatedly to free memory. - Null Return: If the new-handler fails to free
memory, throws an exception, or is absent (
nullptr), the function exits cleanly. Because it is markednoexcept, it does not throwstd::bad_allocand instead returnsnullptrdirectly to the caller.
Critical Caveat: Constructor Exceptions
A new expression performs two distinct operations:
allocating raw memory via operator new, and initializing
the object by invoking its constructor.
The std::nothrow qualifier only applies to the memory
allocation phase. If the allocation succeeds, but the object's
constructor throws an exception during initialization, that exception
will still propagate:
struct Widget {
Widget() {
throw std::runtime_error("Constructor failed");
}
};
// Allocation succeeds, but the constructor throws:
// std::nothrow does NOT catch std::runtime_error.
Widget* w = new (std::nothrow) Widget(); If a constructor throws during a nothrow allocation, the C++ runtime
automatically invokes the matching deallocation function
(operator delete(void*, const std::nothrow_t&)) to free
the allocated memory and prevent a leak before rethrowing the
exception.
Practical Applications
The nothrow version of operator new is primarily used
in:
- Embedded and Real-Time Systems: Environments where
C++ exceptions are disabled (e.g., compiled with
-fno-exceptions). - C Interoperability: Systems interacting heavily with C codebases where stack unwinding across language boundaries is unsafe or unsupported.
- Legacy Codebases: Applications designed around manual error code inspection and pointer validation rather than exception-handling blocks.