是复制构造函数中初始值设定项列表中的make_unique不使用noexcept说明符的好用途

Is make_unique in initializer list in copy constructor good purpose to not use noexcept specifier?

本文关键字:unique noexcept 说明符 make 列表 构造函数 复制      更新时间:2023-10-16

我的复制构造函数旁边有一个noexcept说明符的障碍。

#include <memory>
#include <vector>
class Foo final {
public:
Foo() noexcept = default;
Foo(const Foo& oth) : impl_(std::make_unique<Foo::Impl>()) {} // <---
~Foo() noexcept = default;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
class Foo::Impl {
...
private:
std::vector<int> some_data;
}

我不确定是否应该把noexcept放在复制构造函数旁边,而有std::make_unique可以抛出bad_alloc

任何帮助都将被感激!

cpp编码指南在E.12中对此非常清楚:当由于抛出而退出函数时使用noexcept是不可能的或不可接受的

因此,您可以使用noexcept,即使该函数/ctor的调用可能会导致异常,如果您认为该异常会导致应用程序处于不可处理的状态。

指南示例:

vector<double> munge(const vector<double>& v) noexcept
{
vector<double> v2(v.size());
// ... do something ...
}

这里的noexcept表示我不愿意或不能处理我不能构造局部向量的情况。也就是说,我认为内存耗尽是一个严重的设计错误(与硬件故障一样),所以如果发生这种情况,我愿意让程序崩溃。

因此,如果Foo的失败构造可以使用try-catch块来处理,而不会出现严重问题。那你就不用noexcept了。

反思其他一些答案:如果函数可能抛出,我不会使用noexcept,即使你不在乎程序最终是否会终止。因为如果一个声明为noexcept的函数抛出,它就会抛出。声明函数noexcept为类的用户保存语义信息,他们可能依赖于这些信息,在您的情况下,这基本上是不真实的。

编辑:我建议你阅读Scott Meyers的《有效的现代C++》第14条,它很好地描述了使用noexcept的好处以及何时使用它。

我认为这实际上取决于上下文。如果您能够合理地处理该异常(并期望它被抛出),那么就不应该将其标记为noexcept。另一方面,如果您的程序无法从异常中恢复,您不妨将其标记为noexcept
当要分配的对象很小时,第二种情况是正确的(如果不能分配一个char,则无法进行大量异常处理)。第一个应该发生在真正大的对象上(例如,您可以推迟分配)。话虽如此,如果你正在复制一个巨大的物体。。。为什么不把它移走呢?

简单答案:如果您知道它可能抛出,并且没有充分的理由这样做(例如,如果无法创建副本,您希望应用程序调用std::terminate),则不要声明它为noexcept。

问问自己会有什么收获。当编译器没有异常时,它能优化任何东西吗?我没有看到很多情况会出现这种情况(我看到优化的最常见情况是针对移动,因为std库容器可以使用它——他们检查移动操作是否不例外,这样他们就可以保持保证)。另一个可以使用它的地方是,当您想要记录函数不能抛出的内容时。这里显然不是这样。

另一方面,您将无法再从可能的异常中恢复,您的程序将终止。

所以在夏天,你不会得到任何东西,但你可能会失去一些东西。

还可参考核心指南:

E.12:由于抛出不可能或不可接受而退出函数时使用noexcept

这条规则的标题可能有点令人困惑。它说,如果

  • 它不投掷或
  • 如果出现异常,你不在乎。您愿意使程序崩溃,因为由于内存耗尽,您无法处理std::bad_alloc等异常

如果您是对象的直接所有者,抛出异常不是一个好主意。

顺便说一句,以下特殊成员函数隐式不例外:

  • 默认构造函数
  • 析构函数(据我所知,即使你明确地抛出它)
  • 移动并复制构造函数
  • 移动和复制分配运算符

何时使用noexcept

"最佳实践">

必须考虑是否需要在每个函数声明后附加noexcept,这将大大降低程序员的工作效率(坦率地说,这将是一种痛苦)。

那么,当很明显函数永远不会抛出时,就使用它。

使用noexcept后,我什么时候才能真正观察到性能改进?[…]就我个人而言,我关心noexcept,因为它为编译器提供了更大的自由度,可以安全地应用某些类型的优化。

最大的优化收益似乎来自用户优化,而不是编译器优化,因为可能会检查noexcept并对其进行重载。如果你不使用异常处理方法,大多数编译器都不会受到惩罚,所以我怀疑它会在你的代码的机器代码级别上发生很大变化,尽管可能通过移除处理代码来减小二进制大小。

在big 4中使用noexcept(构造函数、赋值,而不是析构函数,因为它们已经是noexcept了)可能会带来最好的改进,因为noexcept检查在模板代码(如std容器)中是"常见的"。例如,std::vector不会使用类的move,除非它被标记为noexcept(或者编译器可以以其他方式推断它)。