make_shared的CppReference页面显示(与make_unique相同)
可能会抛出 std::bad_alloc 或 T 的构造函数抛出的任何异常。 如果引发异常,则函数不起作用。
这意味着在发生故障时可以抛出 std::bad_alloc 例外。"函数没有效果"隐含地意味着它不能返回 nullptr。如果是这种情况,为什么不是一种常见的做法,总是将 make_shared/make_unique 写入 try catch 块?
使用make_shared的正确方法是什么? 在尝试捕获块内?或正在检查空点?
我认为有两个主要原因。
-
动态内存分配失败通常被视为不允许正常处理的方案。程序终止,仅此而已。这意味着我们通常不会检查每个可能
std::bad_alloc
.或者您是否将std::vector::push_back
包装到一个 try-catch 块中,因为底层分配器可能会抛出? -
并非每个可能的异常都必须在立即呼叫端捕获。有人建议,
throw
与catch
的关系应远大于1。这意味着您可以在更高级别捕获异常,将多个错误路径"收集"到一个处理程序中。T
构造函数抛出的情况也可以这样处理。毕竟,例外是例外。如果在堆上构造对象非常可能抛出,以至于您必须检查每个此类调用,则应考虑使用不同的错误处理方案(std::optional
、std::expected
等)。
无论如何,检查nullptr
绝对不是确保std::make_unique
成功的正确方法。它永远不会返回nullptr
- 要么成功,要么抛出。
投掷bad_alloc
有两个效果:
- 它允许在调用方层次结构中的某个位置捕获和处理错误。
- 它产生明确定义的行为,无论是否发生此类处理。
该明确定义的行为的默认值是通过调用std::terminate()
以快速但有序的方式终止进程。 请注意,它是实现定义的(但是,对于给定的实现,仍然定义良好)堆栈是否在调用terminate()
之前展开。
这与未处理的失败malloc()
有很大不同,例如,(a) 当返回的空指针被取消引用时,会导致未定义的行为,并且 (b) 让执行轻松进行,直到(和之后)那一刻,通常会在此过程中累积进一步的分配失败。
那么,下一个问题是调用代码应该在何处以及如何(如果有的话)捕获和处理异常。
在大多数情况下,答案是不应该。
处理程序要做什么? 实际上有两种选择:
- 以比默认的未经处理的异常处理更有序的方式终止应用程序。
- 在其他地方释放一些内存,然后重试分配。
这两种方法都增加了系统的复杂性(尤其是后者),这需要在特定情况下证明是合理的 - 重要的是,在其他可能的故障模式和缓解措施的背景下。 (例如,已经包含非软件故障保护的关键系统最好快速终止,让这些机制启动,而不是在软件中摸索。
在这两种情况下,在调用方层次结构中执行任何实际处理可能比在分配失败时更有意义。
如果这两种方法都没有增加任何好处,那么最好的方法就是让默认的std::terminate()
处理启动。