功能与按值返回& noexcept

时间:2012-02-13 23:14:08

标签: c++ c++11 return-value noexcept

这个问题是"Constructor with by-value parameter & noexcept"的双重问题。该问题表明,按值函数参数的生命周期管理由调用函数处理;因此调用者处理发生的任何异常,并且被调用函数可以标记自己noexcept。我想知道如何使用noexcept来处理输出结束。

MyType  MyFunction( SomeType const &x ) noexcept;

//...

void  MyCaller()
{
    MyType  test1 = MyFunction( RandomSomeType() );
    MyType  test2{ MyFunction( RandomSomeType() ) };
    //...
    test1 = MyFunction( RandomSomeType() );
    test2 = std::move( MyFunction(RandomSomeType()) );
    MyFunction( RandomSomeType() );  // return value goes to oblivion
}

假设在MyFunction内成功创建了返回值。让我们说MyType 的相应特殊成员函数(复制/移动赋值/构造)可能不会noexcept

  1. RVO / NRVO / Whatever-from-C ++ 11关于从被调用函数向调用者传递返回值的规则意味着无论noexcept状态是什么,传输总是成功。适当的特殊成员函数?
  2. 如果上一个问题的答案是“否”,那么如果返回值转移抛出,是否会对被调用函数或调用者计数异常?
  3. 如果上一个问题的答案是“被叫函数”,则noexcept上的普通MyFunction标记会调用std::terminateMyFunction的{​​{1}}个人资料应更改为什么?当我在Usenet上询问这个问题时,一位受访者认为它应该是noexcept。 (请注意,std::is_nothrow_move_assignable<MyType>::value使用了几种使用返回值的方法,但MyCaller将不知道哪一种正在使用!答案必须涵盖所有情况。)如果{{{} {} 1}}被改为可复制但不可移动?
  4. 因此,如果第二个和第三个问题的最坏情况是准确的,那么如果返回类型具有可抛出的移动,则按值返回的任何函数都不能具有普通MyFunction!现在具有可抛出移动的类型很少,但每次使用按值返回时,模板代码仍然必须使用MyType“脏”。

    我认为让被叫函数负责是坏的:

    noexcept

    至少对我来说,这个问题似乎可以在调用者的结尾处修复(只需用is_nothrow_move_assignable / MyType MyFunction( SomeType const &x ) noexcept( ??? ) { //... try { return SOME_EXPRESSION; // What happens if the creation of SOME_EXPRESSION succeeds, but the // move-assignment (or whatever) transferring the result fails? Is // this try/catch triggered? Or is there no place lexically this // function can block a throwing move!? } catch (...) { return MyType(); // Note that even if default-construction doesn't throw, the // move-assignment may throw (again)! Now what? } } 包装移动赋值),但从被调用函数的末尾开始无法修复。我认为调用者必须处理这个,即使我们需要改变C ++的规则来这样做。或者至少需要某种缺陷报告。

2 个答案:

答案 0 :(得分:3)

要回答部分问题,您可以询问某种类型是否不可构建:

#include <type_traits>

MyType  MyFunction( SomeType const &x )
    noexcept(std::is_nothrow_move_constructible<MyType>::value)
{
  // ....
}

答案 1 :(得分:1)

我认为你的问题很混乱,因为你在谈论从被叫者到调用者的“转移”,而这不是我们在C ++中使用的术语。考虑函数返回值的最简单方法是被调用者通过“返回槽”(由被调用者构造并由调用者销毁的临时对象)与调用者通信。被调用者负责将返回值构造到“返回槽”中,并且调用者负责将值从“返回槽”中取出(如果需要),然后销毁“返回槽”中剩余的任何内容。

MyType MyFunction(SomeType const &x) noexcept
{
    return SOME_EXPRESSION;
}

void MyCaller()
{
    MyType  test1 = MyFunction( RandomSomeType() );  // A
    MyType  test2{ MyFunction( RandomSomeType() ) };  // B
    //...
    test1 = MyFunction( RandomSomeType() );  // C
    test2 = std::move( MyFunction(RandomSomeType()) );  // D
    MyFunction( RandomSomeType() );  // E
}

首先:语句return SOME_EXPRESSION;导致SOME_EXPRESSION的结果移入MyFunction的“返回位置”。此举可能是elided。如果移动没有被删除,那么MyType的移动构造函数将被调用。如果该移动构造函数抛出异常,您可以通过围绕return本身的try-block或通过function try block来捕获异常。

案例AMyFunction里面有一个move-ctor(可能被省略),然后移动到test1(可能被省略)。

案例B:与案例A相同。

案例CMyFunction里面有一个move-ctor(可能被省略),然后将移动分配到test1

案例D:与案例C相同。对std::move的调用并没有带来任何好处,而且编写它的风格很糟糕。

案例EMyFunction里面有一个move-ctor(可能被省略了),就是这样。

如果在move-ctor或move-assignment进入test1期间抛出异常,则可以通过在try-block中包含处理test1的代码来捕获这些异常。 MyFunction内的代码在那时完全无关紧要; MyFunction不知道或不关心调用者将如何处理返回的对象。只有调用者才知道,只有调用者能够捕获调用者生成的异常。