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:

  1. Memory Request: The function attempts to allocate the requested number of bytes via the underlying system allocator (such as malloc or an OS-level heap API).
  2. New-Handler Invocation: If the system allocator returns a null pointer, the runtime checks if a custom std::new_handler has been registered. If present, the handler is invoked repeatedly to free memory.
  3. Null Return: If the new-handler fails to free memory, throws an exception, or is absent (nullptr), the function exits cleanly. Because it is marked noexcept, it does not throw std::bad_alloc and instead returns nullptr directly 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.