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

时间:2019-01-25 08:09:11

标签: c++ c++11 copy-constructor noexcept

我的复制构造函数旁边有一个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

任何帮助将不胜感激!

5 个答案:

答案 0 :(得分:4)

E.12: Use noexcept when exiting a function because of a throw is impossible or unacceptable

中的cpp编码准则非常清楚

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

指南中的示例:

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

此处的noexcept表示我不愿意或无法处理无法构造局部向量的情况。也就是说,我认为内存耗尽是一个严重的设计错误(与硬件故障相当),因此,如果发生这种情况,我愿意使其崩溃。

因此,如果可以使用Foo块来处理try-catch的失败构造,而不会出现严重问题。这样您就不会在其中使用noexcept

答案 1 :(得分:3)

思考其他一些答案:即使某个函数潜在地抛出,我也不会使用noexcept,即使您不在乎程序最终是否会终止也是如此。因为这样会引发声明为noexcept的函数。 声明函数noexcept会为您的类用户保留语义信息,他们可以依赖此信息,在您的情况下,这实际上是不正确的。

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

答案 2 :(得分:1)

我认为这确实取决于具体情况。如果可以合理地处理该异常(并期望将其抛出),则不应将其标记为noexcept。另一方面,如果您的程序无法从异常中恢复,则最好将其标记为noexcept
我要说的第二种情况是,要分配的对象很小(如果您不能分配单个char,则将无法进行很多异常处理)。 第一个应该发生在非常大的对象上(例如,您可以推迟分配)。话虽如此,如果您要复制一个巨大的对象...为什么不移动它呢?

答案 3 :(得分:1)

简单答案:请勿将其声明为noexcept,如果您知道它可能会抛出并且您没有充分的理由这样做(例如,您希望您的应用程序调用std :::如果无法复制,则终止。)

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

另一方面,您将不再能够从可能的异常中恢复,并且程序将终止。

因此,在夏季,您什么也得不到,但是您可能会有所损失。

另请参阅核心准则:

  

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

     

此规则的标题可能会有些混乱。它说您应该将一个函数声明为noexcept,如果

     
      
  • 它不会抛出或
  •   
  • 您不在乎是否有例外情况。您愿意使程序崩溃,因为由于内存耗尽而无法处理诸如std :: bad_alloc之类的异常。
  •   
     

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

顺便说一下,以下特殊成员函数隐式地为noexcept:

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

答案 4 :(得分:1)

何时使用noexcept

“最佳做法”

  

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

然后在明显无法使用该函数的情况下使用它。

  

使用noexcept之后,我什么时候可以实际期望获得性能改善? [...]我个人很关心noexcept,因为为编译器提供了增加的自由度,可以安全地应用某些优化。

似乎最大的优化收益来自用户优化,而不是编译器优化,原因是可以检查noexcept并对其进行重载。大多数编译器都遵循无罚即弃的异常处理方法,因此我怀疑它会在代码的机器代码级别上发生很大的变化(或发生任何变化),尽管也许可以通过删除处理来减少二进制大小代码。

在大四列中使用noexcept(构造函数,赋值,而不是已经使用noexcept的析构函数)可能会带来最佳的改进,因为noexcept检查在模板中是“常见”的代码,例如在std容器中。例如,std::vector不会使用您的类的移动,除非它被标记为noexcept(否则编译器可以推断出来)。