In the following code, there is a no-throw operator new
for the struct TestNew
. It is required to further declare it as noexcept
to ensure that the constructor is not called, when operator new
returns a nullptr
. It is surprising to see the need for explicit noexcept
declaration, as this seems redundant given the std::nothrow_t
tag.
#include <cstdlib>
#include <iostream>
#include <new>
struct TestNew {
void* operator new(std::size_t, std::nothrow_t) noexcept { // Doesn't call the constructor
// void* operator new(std::size_t, std::nothrow_t) { // Unexpected call to constructor
std::cout << "operator new of TestNew called!" << std::endl;
return nullptr;
}
TestNew() {
std::cout << "A TestNew created!" << std::endl;
}
};
int main() {
auto p = new (std::nothrow) TestNew;
if (!p) {
std::cout << "nullptr!" << std::endl;
std::abort();
}
return 0;
}
Both GCC 14.1 and Clang 18.1.0 give the following output when operator new
is not declared as noexcept
:
operator new of TestNew called!
A TestNew created!
nullptr!
With the noexcept
declaration, the output is the following:
operator new of TestNew called!
nullptr!
Both give warnings, though:
GCC: warning: 'operator new' must not return NULL unless it is declared 'throw()' (or '-fcheck-new' is in effect)
Clang: warning: 'operator new' should not return a null pointer unless it is declared 'throw()' or 'noexcept' [-Wnew-returns-null]
Update: The accepted answer has made it clear that there are two necessary aspects for a non-throwing operator new
(whether user implemented or not). One, the std::nothrow_t
tag which is essential to pick the correct function when new (std::nothrow)
is called. Two, the function to be declared as noexcept
, which is expected by the compiler. std::nothrow_t
only does what is expected of a tag type, which is to help with overload resolution. It cannot tell the compiler that a function will not throw exceptions.
When you override operator new
, you have to follow the rules for allocation functions.
An allocation function that has a non-throwing exception specification ([except.spec]) indicates failure by returning a null pointer value.
Any other allocation function never returns a null pointer value and indicates failure only by throwing an exception ([except.throw]) of a type that would match a handler ([except.handle]) of type std::bad_alloc ([bad.alloc]).
[basic.stc.dynamic.allocation]
This isn't a statement about the built-in new
, it is a requirement on all new
s. When you fail to follow the rules, your program has undefined behaviour. That can include constructing an instance of your class at the null address.