如果 make_shared/make_unique 可以抛出 bad_alloc,为什么为它设置一个 try catch 块不是一种常见的做法?

If make_shared/make_unique can throw bad_alloc, why is it not a common practice to have a try catch block for it?

make_shared 的 CppReference 页面说(与 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.

这意味着在失败的情况下可以抛出 std::bad_alloc 异常。 "the functions have no effect" 隐式意味着它不能 return 空指针。如果是这种情况,为什么将 make_shared/make_unique always 写入 try catch 块不是一种常见的做法?

make_shared 的正确使用方法是什么? 在 try catch 块内?或检查 nullptr?

我认为有两个主要原因。

  1. 动态内存分配失败通常被认为是不允许优雅处理的场景。程序终止,仅此而已。这意味着我们通常不会检查所有可能的 std::bad_alloc。或者你是否将 std::vector::push_back 包装到 try-catch 块中,因为底层分配器可能会抛出?

  2. 并非所有可能的异常都必须在直接调用端捕获。有建议 throwcatch 的关系应比 1 大得多。这意味着您在更高级别捕获异常,"collecting" 多个 error-paths 到一个处理程序中。 T 构造函数抛出的 case 也可以这样处理。毕竟,例外是例外。如果在堆上构建对象很可能会抛出异常,以至于您必须检查每次此类调用,则应考虑使用不同的错误处理方案(std::optionalstd::expected 等)。

无论如何,检查 nullptr 绝对 不是 确保 std::make_unique 成功的正确方法。它永远不会 returns nullptr - 要么成功,要么抛出。

投掷bad_alloc有两个效果:

  • 它允许在调用者层次结构中的某处捕获和处理错误。
  • 它会产生 well-defined 行为,无论是否发生这种处理。

well-defined 行为的默认设置是进程通过调用 std::terminate() 以快速但有序的方式终止。请注意,在调用 terminate().

之前堆栈是否展开是 implementation-defined(但是,对于给定的实现,仍然是 well-defined)

这与未处理的失败 malloc() 有很大不同,例如,(a) 当取消引用返回的空指针时会导致未定义的行为,并且 (b) 让执行愉快地进行直到(和超越)那一刻,通常会在此过程中累积更多的分配失败。

接下来的问题是调用代码应在何处以及如何(如果有的话)捕获并处理异常。

大多数情况下答案是不应该。

管理员要做什么?真的有两个选择:

  • 以比默认的未处理异常处理更有序的方式终止应用程序。
  • 在其他地方释放一些内存并重试分配。

这两种方法都增加了系统的复杂性(尤其是后者),这需要在特定情况下进行论证——重要的是,在其他可能的故障模式和缓解措施的背景下。 (例如,一个已经包含 non-software 故障保护的关键系统可能最好快速终止以让这些机制启动,而不是在软件中四处乱窜。)

在这两种情况下,与进行失败分配的点相比,在调用者层次结构中更高层完成任何实际处理可能更有意义。

如果这些方法都没有带来任何好处,那么最好的方法就是让默认的 std::terminate() 处理开始。