如何结合std::make_shared和new(std::nothrow)?

18

C++的new运算符有一个选项,当分配失败时可以返回一个空指针而不是抛出bad_alloc异常。

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

是的,我理解这只能防止新对象抛出bad_alloc异常,但它不能防止Foo构造函数抛出异常。

如果你想使用shared_ptr而不是裸指针,通常应该使用make_shared,因为它在控制块的分配方面更加聪明。

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

make_shared封装了new操作,这使得无法选择nothrow版本。因此,似乎必须放弃使用make_shared并显式调用new。

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

这样做就可以避免使用Foo和控制块进行优化,而且控制块的分配可能会独立于Foo的分配失败,但我不想关注这一点。假设控制块很小,因此在实践中其分配永远不会失败。我担心的是无法为Foo分配空间。

有没有一种方法可以保留make_shared的单次分配优势,同时保留仅获得空指针而不是bad_alloc异常的能力来分配Foo的空间?


1
你可能需要复制make_shared的代码,然后根据需要进行修改。 - Remy Lebeau
2个回答

6

看起来你可以使用allocate_shared,并传入一个使用nothrow new的分配器来解决这个问题。


1
注意:allocated_shared - Matthieu M.
3
因为我认为这会违反分配器的要求(§17.6.3.5表28),所以我投了反对票。如果系统内存不足,这种解决方案肯定会出现严重问题。例如,MSVC12的allocate_shared()没有检查allocate()返回的指针,并会在(void*)0处愉快地构造控制块。第一次尝试引用此指针很可能会失败。(幸运的是,在MSVC12上这已经发生在allocate_shared()中)。 - Brandlingo
@Matthäus Brandl我在那个表格(C++11草案标准)中没有看到任何可能导致分配器要求破坏的内容,你能再详细解释一下吗? - Mark B
2
@MarkB 在当前草案中,表29列出了对于表达式a.allocate(n)的效果:“为n个类型为T的对象分配内存,但不构造对象。allocate可能会引发适当的异常。”这无疑并不十分明确,但它说明在a.allocate()之后已经分配了内存。没有选择不分配并返回nullptr的选项。它可能会抛出异常,在这种情况下,该后置条件可能不成立。 - Brandlingo
1
与此同时,有一个相关的问题被提出,其中被接受的答案得出了相同的结论。问题链接:https://dev59.com/-6vka4cB1Zd3GeqPr1pe,答案链接:https://dev59.com/-6vka4cB1Zd3GeqPr1pe#50326956。 - Brandlingo

2

只需创建一个自定义的nake_foo并捕获异常:

#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>(); }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接