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 news. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With