Функция с возвращаемым значением & noexcept - PullRequest
4 голосов
/ 14 февраля 2012

Этот вопрос является двойным из "Конструктор с параметром по значению & 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 /Какие бы правила C ++ 11 не относились к передаче возвращаемых значений из вызываемой функции вызывающей стороне, это означает, что передача всегда завершается успешно, независимо от состояния noexcept соответствующей специальной функции-члена?
  2. Если ответ на предыдущий вопрос «нет», то, если возвращается возвращаемое значение, засчитывается ли исключение против вызываемой функции или вызывающей стороны?
  3. Если ответ на предыдущий вопрос «вызываемый»функция ", то простой маркер noexcept на MyFunction вызовет std::terminate.На что должен быть изменен профиль MyFunction noexcept?Когда я спросил об этом в Usenet, респондент подумал, что это должно быть std::is_nothrow_move_assignable<MyType>::value.(Обратите внимание, что MyCaller использовал несколько методов использования возвращаемого значения, но MyFunction не будет знать, какой из них используется! Ответ должен охватывать все случаи.) Имеет ли значение, если MyType изменяется набыть копируемым, но неподвижным?

Так что, если наихудшие случаи второго и третьего вопросов точны, то любая функция, которая возвращает значение, не может иметь простое значение noexcept, если возвращаемое значениетип имеет броский ход!Теперь типы с перемещаемыми значениями типа throwed должны быть редкими, но код шаблона по-прежнему должен «загрязнять» себя с помощью 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?
    }
}

Эта проблема, по крайней мере, мне кажется, решаемой в конце вызывающей стороны (просто оберните назначение перемещения с помощью try / catch), но не решаемой с конца вызываемой функции,Я думаю, что вызывающая сторона должна справиться с этим, даже если для этого нам нужно изменить правила C ++.Или, по крайней мере, необходим какой-то отчет о дефектах.

Ответы [ 2 ]

3 голосов
/ 14 февраля 2012

Чтобы ответить на часть вашего вопроса, вы можете спросить, может ли определенный тип быть конструктивным:

#include <type_traits>

MyType  MyFunction( SomeType const &x )
    noexcept(std::is_nothrow_move_constructible<MyType>::value)
{
  // ....
}
1 голос
/ 20 мая 2015

Я думаю, что ваш вопрос сбит с толку тем, что вы говорите о «переводе» от вызываемого к вызывающему, и это не термин, который мы используем в 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. Если этот конструктор перемещения выдает исключение, вы можете перехватить исключение через блок try вокруг самого return или с помощью блока try function .

Кейс A: Внутри MyFunction (который может быть исключен) находится ход-ctor, а затем ctor-ход в test1 (который может быть исключен).

Дело B: То же, что и дело A.

Дело C: Внутри MyFunction (который может быть исключен) есть ход-ctor, а затем назначение перемещения в test1.

Дело D: То же, что и дело C. Звонок на std::move не приносит никакой пользы, и писать его нехорошо.

Дело E: Внутри MyFunction (который может быть исключен) есть ход-ctor, и все.

Если исключения генерируются во время перемещения-ctor или присваивания-перемещения в test1, вы можете поймать их, поместив код, связанный с test1, в блок try. Код внутри MyFunction совершенно не имеет значения в этой точке; MyFunction не знает или не заботится о том, что будет делать вызывающая сторона с возвращенным объектом. Только звонящий знает, и только звонящий может перехватывать исключения, сгенерированные звонящим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...