如果 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?
我认为有两个主要原因。
动态内存分配失败通常被认为是不允许优雅处理的场景。程序终止,仅此而已。这意味着我们通常不会检查所有可能的 std::bad_alloc
。或者你是否将 std::vector::push_back
包装到 try-catch 块中,因为底层分配器可能会抛出?
并非所有可能的异常都必须在直接调用端捕获。有建议 throw
与 catch
的关系应比 1 大得多。这意味着您在更高级别捕获异常,"collecting" 多个 error-paths 到一个处理程序中。 T
构造函数抛出的 case 也可以这样处理。毕竟,例外是例外。如果在堆上构建对象很可能会抛出异常,以至于您必须检查每次此类调用,则应考虑使用不同的错误处理方案(std::optional
、std::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()
处理开始。
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?
我认为有两个主要原因。
动态内存分配失败通常被认为是不允许优雅处理的场景。程序终止,仅此而已。这意味着我们通常不会检查所有可能的
std::bad_alloc
。或者你是否将std::vector::push_back
包装到 try-catch 块中,因为底层分配器可能会抛出?并非所有可能的异常都必须在直接调用端捕获。有建议
throw
与catch
的关系应比 1 大得多。这意味着您在更高级别捕获异常,"collecting" 多个 error-paths 到一个处理程序中。T
构造函数抛出的 case 也可以这样处理。毕竟,例外是例外。如果在堆上构建对象很可能会抛出异常,以至于您必须检查每次此类调用,则应考虑使用不同的错误处理方案(std::optional
、std::expected
等)。
无论如何,检查 nullptr
绝对 不是 确保 std::make_unique
成功的正确方法。它永远不会 returns nullptr
- 要么成功,要么抛出。
投掷bad_alloc
有两个效果:
- 它允许在调用者层次结构中的某处捕获和处理错误。
- 它会产生 well-defined 行为,无论是否发生这种处理。
well-defined 行为的默认设置是进程通过调用 std::terminate()
以快速但有序的方式终止。请注意,在调用 terminate()
.
这与未处理的失败 malloc()
有很大不同,例如,(a) 当取消引用返回的空指针时会导致未定义的行为,并且 (b) 让执行愉快地进行直到(和超越)那一刻,通常会在此过程中累积更多的分配失败。
接下来的问题是调用代码应在何处以及如何(如果有的话)捕获并处理异常。
大多数情况下答案是不应该。
管理员要做什么?真的有两个选择:
- 以比默认的未处理异常处理更有序的方式终止应用程序。
- 在其他地方释放一些内存并重试分配。
这两种方法都增加了系统的复杂性(尤其是后者),这需要在特定情况下进行论证——重要的是,在其他可能的故障模式和缓解措施的背景下。 (例如,一个已经包含 non-software 故障保护的关键系统可能最好快速终止以让这些机制启动,而不是在软件中四处乱窜。)
在这两种情况下,与进行失败分配的点相比,在调用者层次结构中更高层完成任何实际处理可能更有意义。
如果这些方法都没有带来任何好处,那么最好的方法就是让默认的 std::terminate()
处理开始。