Функция с возвращаемым значением & noexcept
Этот вопрос двойственный: "Конструктор с параметром по значению & 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
,
- Означают ли правила RVO/NRVO/Wh what-from-C++11, касающиеся передачи возвращаемых значений из вызываемой функции вызывающей стороне, то, что передача всегда завершается успешно, независимо от
noexcept
статус соответствующей специальной функции-члена? - Если ответ на предыдущий вопрос "нет", то, если возвращается возвращаемое значение, засчитывается ли исключение против вызываемой функции или вызывающей стороны?
- Если ответ на предыдущий вопрос - "вызываемая функция", тогда
noexcept
маркер наMyFunction
вызоветstd::terminate
, Что должноMyFunction
"snoexcept
профиль будет изменен на? Когда я спросил об этом в Usenet, респондент подумал, что это должно бытьstd::is_nothrow_move_assignable<MyType>::value
, (Обратите внимание, чтоMyCaller
использовал несколько методов использования возвращаемого значения, но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?
}
}
Эта проблема, по крайней мере для меня, кажется, решаема в конце вызывающей стороны (просто оберните перемещение-назначение с помощью try
/catch
) но нельзя исправить с конца вызываемой функции. Я думаю, что вызывающая сторона должна справиться с этим, даже если для этого нам нужно изменить правила C++. Или, по крайней мере, необходим какой-то отчет о дефектах.
2 ответа
Чтобы ответить на часть вашего вопроса, вы можете спросить, может ли определенный тип быть конструктивным:
#include <type_traits>
MyType MyFunction( SomeType const &x )
noexcept(std::is_nothrow_move_constructible<MyType>::value)
{
// ....
}
Я думаю, что ваш вопрос сбит с толку тем, что вы говорите о "переводе" от вызываемого к вызывающему, и это не термин, который мы используем в 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
, Этот ход может быть опущен. Если ход не исключен, то MyType
Move-конструктор будет вызван. Если этот конструктор перемещения выдает исключение, вы можете перехватить исключение с помощью блока try вокруг return
сам или через блок try функции.
случай A
: Есть движение-ctor внутри MyFunction
(который может быть исключен), а затем переместить test1
(который может быть исключен).
случай B
: То же, что и в случае A
,
случай C
: Есть движение-ctor внутри MyFunction
(который может быть исключен), а затем перемещение-назначение в test1
,
случай D
: То же, что и в случае C
, Призыв к std::move
не дает никакой пользы, и писать это плохо.
случай E
: Есть движение-ctor внутри MyFunction
(который может быть исключен), и это все.
Если исключения генерируются во время перемещения-перемещения или перемещения-назначения в test1
, вы можете поймать их, упаковав код, имеющий дело с test1
в пробном блоке. Код внутри MyFunction
совершенно не имеет значения в этой точке; MyFunction
не знает или не заботится о том, что будет делать вызывающий объект с возвращенным объектом. Только вызывающий знает, и только вызывающий может отловить исключения, сгенерированные вызывающим.