异常保证和按值传递

Nir*_*man 5 c++

我最近在多种情况下遇到过这个问题,其中表达的一些观点让我感到惊讶。这是第一个简单的例子:

void f(std::vector<double> x) {};
Run Code Online (Sandbox Code Playgroud)

问题是:记录或描述f提供不抛出保证是否可以接受?同样,我怀疑由于异常不是从 f 的主体生成的,因此使用noexcept在技术上是合理的。但应该标记它吗noexcept?例如,一个优化版本以set某种方式发现添加模板化比较器不得抛出的要求很有用。它在编译时使用静态断言检测到这一点并导致错误。然而,有人可以为按值获取的向量编写一个比较器,并将其标记为 noexcept,并将其与此版本的set. 如果这导致了不良行为,那么这是容器作者的错吗?或者将比较器标记为 no except 的人?

再举一个涉及另一种异常保证类型的示例,请考虑:

void g(std::vector<double> x, std::unique_ptr<int> y);
Run Code Online (Sandbox Code Playgroud)

该功能能否提供强有力的保证?

std::vector<double> p{1.0, 2.0};
auto q = std::make_unique<int>(0);
bool func_successful = true;

try {
  g(p, std::move(q));
}
catch (...) {
  func_successful = false;
}

if (!func_successful)
  assert(q);
Run Code Online (Sandbox Code Playgroud)

我认为,如果断言可能失败,那么 g 不会提供强有力的保证,因为q在调用 g 之前立即不为空(记住std::move实际上并没有移动任何东西)。它可能会失败:参数评估的顺序未指定,因此可能首先构造 y 清空 q,然后构造 x 并抛出。即使从技术上讲它不是发生在函数体中,而是发生在调用函数的行为中,该函数仍然对此负责吗?

编辑:我要提到 Herb Sutter 在这里讨论了这个问题:https://youtu.be/xnqTKD8uD64 ?t=1h11m56s 。他说,将这样的函数标记为 noexcept 是“有问题的”;我希望得到更详细的答复。

Nic*_*las 2

问题是:将 f 记录或描述为提供不抛出保证是否可以接受?

C++ 标准并不认为是这样。考虑应用于赋值时对 nothrow 的处理。

is_assignable如果对于T和U,此表达式有效: ,则为真std::declval<T>() = std::declval<U>()。如果该表达式整体为 noexcept,is_nothrow_assignable则为 true 。复制/移动分配也是如此(显然具有相同的类型)。

参数的初始化operator=是表达式的一部分。因此,如果该初始化可以抛出,那么该赋值就不是不抛出。有一篇工作组论文深入讨论了这个问题(主要围绕移动分配)。

是否应该标记此功能的问题noexcept是另一回事。但函数本身noexcept不应该与函数调用表达式的某些部分永远不会抛出的信念相混淆。

我个人对此事的感觉是,您不应该noexcept从一开始就标记随机函数。您应该将它们与特殊成员函数和其他明确需要使用它的非常特定的地方一起使用。也就是说,它应该不仅仅是用户的语义标记。noexcept当某人实际上要进行元编程并根据是否是 来选择调用或不调用该函数时使用noexcept。