Reputation: 546
The CppReference page for make_shared says (same with make_unique)
May throw std::bad_alloc or any exception thrown by the constructor of T. If an exception is thrown, the functions have no effect.
This means that std::bad_alloc exeception can be thrown in case of a failure. "the functions have no effect" implicitly means that it cannot return a nullptr. If this is the case, why is it not a common practice to write make_shared/make_unique always into a try catch block?
What is the proper way to use a make_shared? Within try catch block? or Checking for nullptr?
Upvotes: 7
Views: 3694
Reputation: 5311
Throwing bad_alloc
has two effects:
The default for that well-defined behaviour is for the process to terminate in an expedited but orderly manner by calling std::terminate()
. Note that it is implementation-defined (but, for a given implementation, well-defined nonetheless) whether the stack is unwound before the call to terminate()
.
This is rather different from an unhandled failed malloc()
, for example, which (a) results in undefined behaviour when the returned null pointer is dereferenced, and (b) lets execution carry on blithely until (and beyond) that moment, usually accumulating further allocation failures along the way.
The next question, then, is where and how, if at all, calling code should catch and handle the exception.
The answer in most cases is that it shouldn't.
What's the handler going to do? Really there are two options:
Both approaches add complexity to the system (the latter especially), which needs to be justified in the specific circumstances - and, importantly, in the context of other possible failure modes and mitigations. (e.g. A critical system that already contains non-software failsafes might be better off terminating quickly to let those mechanisms kick in, rather than futzing around in software.)
In both cases, it likely makes more sense for any actual handling to be done higher up in the caller hierarchy than at the point making the failed allocation.
And if neither of these approaches adds any benefit, then the best approach is simply to let the default std::terminate()
handling kick in.
Upvotes: 3
Reputation: 38267
I see two main reasons.
Failure of dynamic memory allocation is often considered a scenario which doesn't allow for graceful treatment. The program is terminated, and that's it. This implies that we often don't check for every possible std::bad_alloc
. Or do you wrap std::vector::push_back
into a try-catch block because the underlying allocator could throw?
Not every possible exception must be caught right at the immediate call side. There are recommendations that the relation of throw
to catch
shall be much larger than one. This implies that you catch exceptions at a higher level, "collecting" multiple error-paths into one handler. The case that the T
constructor throws can also be treated this way. After all, exceptions are exceptional. If construction of objects on the heap is so likely to throw that you have to check every such invocation, you should consider using a different error handling scheme (std::optional
, std::expected
etc.).
In any case, checking for nullptr
is definitely not the right way of making sure std::make_unique
succeeds. It never returns nullptr
- either it succeeds, or it throws.
Upvotes: 7