这个问题是"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
。
noexcept
状态是什么,传输总是成功。适当的特殊成员函数?noexcept
上的普通MyFunction
标记会调用std::terminate
。 MyFunction
的{{1}}个人资料应更改为什么?当我在Usenet上询问这个问题时,一位受访者认为它应该是noexcept
。 (请注意,std::is_nothrow_move_assignable<MyType>::value
使用了几种使用返回值的方法,但MyCaller
将不知道哪一种正在使用!答案必须涵盖所有情况。)如果{{{} {} 1}}被改为可复制但不可移动?因此,如果第二个和第三个问题的最坏情况是准确的,那么如果返回类型具有可抛出的移动,则按值返回的任何函数都不能具有普通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 ++的规则来这样做。或者至少需要某种缺陷报告。
答案 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来捕获异常。
案例A
:MyFunction
里面有一个move-ctor(可能被省略),然后移动到test1
(可能被省略)。
案例B
:与案例A
相同。
案例C
:MyFunction
里面有一个move-ctor(可能被省略),然后将移动分配到test1
。
案例D
:与案例C
相同。对std::move
的调用并没有带来任何好处,而且编写它的风格很糟糕。
案例E
:MyFunction
里面有一个move-ctor(可能被省略了),就是这样。
如果在move-ctor或move-assignment进入test1
期间抛出异常,则可以通过在try-block中包含处理test1
的代码来捕获这些异常。 MyFunction
内的代码在那时完全无关紧要; MyFunction
不知道或不关心调用者将如何处理返回的对象。只有调用者才知道,只有调用者能够捕获调用者生成的异常。