How to combine std::make_shared and new(std::nothrow)
Asked Answered
S

2

18

C++'s new has an option to return a null pointer instead of throwing a bad_alloc exception when an allocation failed.

Foo * pf = new(std::nothrow) Foo(1, 2, 3);

(Yes, I understand this only prevents the new from throwing a bad_alloc; it doesn't prevent Foo's constructor from throwing an exception.)

If you want to use a shared pointer instead of a raw pointer, you generally should use make_shared, as it gets clever about the allocation of the control block.

auto pf = std::make_shared<Foo>(1, 2, 3);

make_shared encapsulates the new, which makes it impossible(?) to choose the nothrow version. So it seems you have to abandon make_shared and call new explicitly.

std::shared_ptr<Foo> pf(new(std::nothrow) Foo(1, 2, 3));

This eliminates the optimization of allocating the control block with the Foo, and the control block allocation could fail independently of the Foo allocation, but I don't want to focus on that. Let's assume the control block is small so its allocation will never fail in practice. It's the failure to allocate space for the Foo that concerns me.

Is there a way to get the single-allocation advantage of make_shared while retaining the ability to simply get a null pointer instead of a bad_alloc exception when allocating space for the Foo?

Shriner answered 19/3, 2014 at 15:49 Comment(1)
You will likely have to make a copy of make_shared's code and then modify it as needed.Expiatory
K
6

It looks like allocate_shared, passing in an allocator that uses the nothrow new should do the trick for you.

Kesterson answered 19/3, 2014 at 16:23 Comment(5)
Note: allocated_sharedSpringtime
Voted down because this would IMHO break the allocator requirements. (§17.6.3.5 table 28) This solution will for sure go horribly wrong if the system runs out of memory. allocate_shared() of MSVC12 for example does not check the pointer returned by allocate() and would happily construct the control block at (void*)0. The first attempt to dereference this pointer will most likely fail. (fortunately on MSVC12 this happens already in allocate_shared()).Deach
@Matthäus Brandl I didn't see anything in that table (C++11 draft standard) that would cause this to break the allocator requirements, could you elaborate a bit more?Kesterson
@MarkB In the current draft table 29 lists for expression a.allocate(n) the effect "Memory is allocated for n objects of type T but objects are not constructed. allocate may raise an appropriate exception.". This is undoubtedly not quite explicit but it states that after a.allocate() memory is allocated. There is no choice to not allocate and return nullptr. It may throw an exception in which case that post condition need not hold.Deach
In the meantime an according question was asked where the accepted answer comes to the same conclusion.Deach
D
3

Just have a custom nake_foo and catch the exception:

#include <memory>

struct Foo {
    Foo(int, int, int) { throw std::bad_alloc(); }
};

std::shared_ptr<Foo> make_foo(int a, int b, int c) {
    try { return std::make_shared<Foo>(a, b, c); }
    catch (const std::bad_alloc&) { return std::shared_ptr<Foo>(); }
}
Darnelldarner answered 19/3, 2014 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.