0

It has been asked whether set_new_handler is safe in a multi-threaded environment. I would like to know if C++11 and later implementations of the C++ standard library use the thread_local feature to store the std::new_handler. If implementations do not use thread_local, why not? That would seem to make the feature a bit more robust in multi-threaded programs.

I also don't really see how set_new_handler would work for a class that overloads new and sets a std::new_handler and creates (and owns reference to) an object that also overloads new and sets its own std::new_handler. I would expect that would be especially egregious if the owning class has options to reap/free memory, while the owned object decides to call abort/terminate.

I expect the usual advice is to use nothrowversion of new and forget set_new_handler. If that is a universal opinion, why isn't set_new_handler a deprecated feature in the standard library?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
FogRising
  • 1
  • 2
  • 1
    Your question doesn't really make sense. `set_new_handler` sets the handler for the default memory allocator should it fail. It isn't supposed to set it per-thread, and I don't see how that would affect the robustness of a program "in multi-threaded environments". Equally importantly, `set_new_handler` basically has nothing to do with classes that overload `new`. New handlers are not scoped to a particular class, and `set_new_handler` only has dominion over the default allocators, not those you override yourself. – Nicol Bolas Sep 07 '19 at 23:08
  • Scott Meyers shows an example in Effective C++ in Item 49 on how you might change std::new_handler just within the context of a class C's overload of new operator. The basic pattern is to substitute the std::new_handler at the top of the new overload, and revert its state on the way out (best practice with an RAII handle). So within a multi-threaded program this could definitely lead to unexpected behavior if more than one class attempts to do this and new overloads are not somehow globally synchronized. – FogRising Sep 08 '19 at 16:16
  • Effective C++ predates C++11, and therefore predates threading as a concept in the C++ core language and library. Pre-C++11, the behavior of `new` with regard to threading was not undefined; it wasn't even a thing, because the concept of "threading" wasn't something the standard even considered. So it's not surprising that an item that never took threading into account would suddenly not be viable once you start taking threading into account. – Nicol Bolas Sep 08 '19 at 16:26
  • @NicolBolas Can you explain what you mean by "Pre-C++11, the behavior of new with regard to threading was not undefined; it wasn't even a thing,"? I know of lots of C++ code written in the era before C++11 that used POSIX threads or Boost::threads. Global _::operator new_ has been safe in multi-threaded code on many platforms/compilers long before C++0x/11. That said, I don't disagree that std::set_new_handler may have never been safe for threads (and may still not be safe), thus why I am trying to get clarification on its implementation! – FogRising Sep 08 '19 at 18:15
  • "*I know of lots of C++ code written in the era before C++11 that used POSIX threads or Boost::threads.*" Sure, but as far as the *standard* was concerned, [threading was not even a question you could ask](https://stackoverflow.com/a/6319356/734069). Asking the standard if `operator new` was thread-safe was like asking a rainbow if the color red means love. – Nicol Bolas Sep 08 '19 at 18:19
  • @NicolBolas, I hate to sound argumentative, but I don't remember asking about rainbows and unicorns. I asked if in *C++11* does the _std::set_new_handler/get_new_handler_ take advantage of _thread_local_ obviously a *C++11* feature, and if it doesn't why not? Perhaps there is some technical reason it cannot or should not... – FogRising Sep 08 '19 at 19:54

1 Answers1

2

The new_handler is not, and was never expected to be, a thread-local construct. It was always global and that is by design.

The new handler is intended to be set once and left alone; it's not meant for temporary changes that are local to a single execution thread. You can build such a facility for yourself, by creating a new handler that defers its operations to a thread_local variable which your code can set. But the C++ new handler feature itself is not meant for such use cases.

Now, one could say that this was a legacy decision. C++98/03's memory model does not consider the possibility of threading. So any pre-C++11 threaded implementations essentially decided for themselves which operations were thread-safe, were thread-local, and so forth. Implementations that decided to implement the new-handler as a global construct were no less correct than implementations that made it per-thread. Indeed, implementations probably settled on the global option at some point, and C++11 just adopted it. And changing it to be thread-local would break peoples' code.

But at the same time, the point of the new handler is to allow the user to change the default handling of allocation failures. Default error handing is not a thread-local concept; it's something that is universal throughout the program. Much like the termination function and so forth, there is precisely one that is expected to be used.

To put it simply, code that used a class-local new handler was always wrong to some degree, even in the C++98/03 days. It's just that until C++11 standardized threading, it was something that you could get away with (at least as far as the standard was concerned). And it isn't deprecated because it is useful for its intended purpose. nothrow is only useful locally; set_new_handler is useful globally.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • @Quentin: The OP specifically mentioned classes whose `operator new` overloads change the new handler. Which is why I said "class-local". – Nicol Bolas Sep 09 '19 at 16:03
  • Oops, my bad! The `operator new` part slipped out of my mind while I was reading your answer and I assumed you had made a typo. – Quentin Sep 09 '19 at 16:22