【问题标题】:Marking a function `noexcept` that could cause an exception constructing the returned object标记可能导致构造返回对象的异常的函数“noexcept”
【发布时间】:2021-01-04 17:51:49
【问题描述】:

考虑这个函数:

std::vector<unsigned> copy(std::vector<unsigned> const& v) noexcept
{ return v; }

int main()
{
   try {
       (void)copy({1, 2, 3});
   } catch(...) {}
}

返回对象的副本构造可能会抛出。在这种情况下,异常是否会传播给调用者(即它被认为发生在main 中)并因此将在catch(...) 处理程序中处理?或者异常会遇到noexcept 并导致调用std::terminate()

C++17/C++20 中关于生命周期规则的更改(标准化 RVO、临时实现、隐式对象创建等)是否在这方面相对于以前版本的标准改变了一些规则?

【问题讨论】:

  • 从 noexcept 函数返回时复制构造函数抛出调用 std::terminate,因此可能不太好 godbolt.org/z/seMbG1(尽管这不能代替标准中的引用)
  • “合法”是什么意思?为什么不合法?
  • @Barry 措辞改进。

标签: c++ exception c++17 language-lawyer c++20


【解决方案1】:

C++17 有一个措辞更改,在 return 语句周围添加了排序。添加了以下段落。

[stmt.return]

3调用结果的拷贝初始化是有序的 在结束临时人员销毁之前 由 return 语句的操作数建立的完整表达式, 反过来,它在局部变量的破坏之前被排序 ([stmt.jump]) 包含 return 语句的块。

结果对象在作用域内的局部变量被销毁之前被初始化。这意味着 throw 在函数的范围内。因此,此时抛出的任何异常都不在调用方。

因此,将函数标记为noexcept 将使程序终止。

RVO 不会改变这一点。它只影响结果对象在哪个存储中初始化,但初始化本身仍然是函数执行的一部分。

【讨论】:

  • 我也相当肯定所有这一切也是 C++17 之前的预期行为,其中的措辞使其更加清晰。但是,我无法找到我最初阅读该意图的地方。
  • +1 对于返回语句序列,我懒得从标准中查找和引用。更好更正确的答案。
  • 我基本上同意这个答案中的所有内容。但是,RVO 确实在一定程度上有所帮助,因为它可以避免任何复制构造。虽然有问题的代码不是这种情况......
【解决方案2】:

在这种情况下,异常是否会传播给调用者(即它被认为发生在 main 中)并因此将在 catch(...) 处理程序中处理?

我不同意。复制必须在函数范围内完成,作为 return 语句表达式的一部分。因为局部析构函数只有在返回后才会被调用,并且它们是在函数范围内定义的。

是的,C++17 对 RVO 做了一些保证,特别是这个例子现在保证省略:

struct Foo{};

Foo bar(){
    Foo local;
    return Foo{};
    // Foo:~Foo(local);
}

Foo var = bar();

不过,如果 Foo:Foo() 抛出,该函数不是 noexcept。所有 RVO 都说没有移动也没有复制到 var 变量和 Foo{} 表达式在位置 var 构造对象。然而,在构造对象时它不会改变 - 在函数范围内,甚至在调用析构函数之前。

此外,强制性 RVO 不适用于此处,因为 v 不是纯右值,而是左值。

【讨论】:

  • 我编辑了您的答案,因为我更改了问题的措辞;所以我编辑了你的匹配。我希望你没事。
  • 巴里编辑了我的问题,所以我又编辑了你的答案哈哈
猜你喜欢
  • 2016-07-13
  • 1970-01-01
  • 2015-05-12
  • 1970-01-01
  • 2013-03-25
  • 1970-01-01
  • 1970-01-01
  • 2015-07-25
  • 2020-09-22
相关资源
最近更新 更多