std::set_new_handler in C++: Handling Out of Memory
In C++, dynamic memory allocation failures typically result in a
std::bad_alloc exception being thrown by
operator new. However, real-time, embedded, and
mission-critical applications often require deterministic behavior or
centralized recovery mechanisms before an exception propagates. This
article explains the role of std::set_new_handler, how it
integrates into the memory allocation lifecycle, the required
responsibilities of a custom handler function, and how to safely manage
memory exhaustion.
The Role of
std::set_new_handler
Declared in the <new> header,
std::set_new_handler registers a user-defined callback
function (a std::new_handler) that the default allocation
functions invoke when an allocation request cannot be satisfied.
When operator new fails to allocate the requested bytes
from the free store, it does not immediately throw an exception.
Instead, it checks whether a new-handler function has been registered.
If one exists, operator new calls this handler, loops back,
and retries the allocation. This loop continues until the allocation
succeeds, the handler terminates the process, or the handler throws an
exception.
Required Actions of a New-Handler
Because operator new repeatedly invokes the installed
handler in an infinite loop until memory becomes available, a custom
handler must execute one of the following specific actions to prevent an
infinite loop:
- Make more memory available: The handler can release
pre-allocated reserve pools, clear in-memory caches, or free unused
subsystems. Upon returning,
operator newwill attempt the allocation again, which may now succeed. - Install a different handler: If the current handler
cannot free memory, it can call
std::set_new_handlerto register a fallback handler capable of taking alternative steps. - Uninstall the handler: Passing
nullptrtostd::set_new_handlerremoves the current handler. On the subsequent retry,operator newsees no registered handler and throwsstd::bad_alloc. - Throw an exception: The handler can explicitly
throw
std::bad_allocor an exception derived from it.operator newwill catch nothing and allow the exception to propagate out to the caller. - Terminate execution: For systems that cannot
recover from out-of-memory states, the handler can log diagnostic
information to a low-overhead output stream and safely shut down via
std::abort()orstd::exit().
Implementation Example
The following pattern demonstrates how to install a custom handler that logs the error, frees emergency reserve memory, and unregisters itself to prevent deadlocks:
#include <iostream>
#include <new>
#include <cstdlib>
char* g_emergency_reserve = nullptr;
void custom_new_handler() {
// Release reserve memory to allow critical operations or logging to complete
if (g_emergency_reserve != nullptr) {
delete[] g_emergency_reserve;
g_emergency_reserve = nullptr;
std::cerr << "[Memory Alert] Emergency memory freed. Retrying allocation.\n";
return; // Returns to operator new to retry allocation
}
// If reserve is already exhausted, abort or unregister
std::cerr << "[Fatal Error] Out of memory. Aborting execution.\n";
std::set_new_handler(nullptr); // Ensure std::bad_alloc is thrown next, or terminate directly
std::abort();
}
int main() {
// Allocate emergency memory reserve
g_emergency_reserve = new char[1024 * 1024 * 64]; // 64 MB reserve
// Register the custom handler
std::set_new_handler(custom_new_handler);
// Code that triggers massive memory consumption
// ...
return 0;
}Class-Specific Memory Handling
While std::set_new_handler configures a global hook, C++
also supports class-specific handlers. By overloading
operator new and providing custom
set_new_handler routines for individual classes, developers
can tailor out-of-memory responses depending on the operational
significance of the object being allocated.