为什么在这种情况下调用移动构造函数?

Why is the move constructor called in this case?

我有以下代码:

SomeClass func()
{
    SomeClass someObject;
    someObject.mutate("some text");
    return someObject;
}

int main()
{
    func();
    return 0;
}

其中 SomeClass 仅在构造函数中记录一些内容,以便我可以验证正在调用的内容。

对于发布版本,我有以下输出:

default constructor

由于 copy/move 省略,这是有道理的。 我想关闭 Return 值优化。 通过调试版本,我得到以下输出:

default constructor
move constructor

我认为我可以安全地假设 NRVO si 关闭。 我真的很想知道为什么调用移动构造函数而不是复制构造函数。我的(可能是错误的)理解是,由于 func 中的 someObject 是左值,因此 return 对象应该使用复制构造函数而不是移动构造函数来初始化。

我错过了什么?有人可以指出标准中阐明该案例的段落吗?

来自 cppreference.com return [expression]; :

If [expression] is an lvalue expression that is the (possibly parenthesized) name of an automatic storage duration object declared in the body or as a parameter of the innermost enclosing function or lambda expression, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if [expression] were an rvalue expression (thus it may select the move constructor), and if no suitable conversion is available, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed a second time, with [expression] considered as an lvalue (so it may select the copy constructor taking a reference to non-const).

简而言之,自 C++11 起,return 语句将尽可能使用移动构造函数并回退到复制构造函数。

同样来自 C++11 标准,12.8 复制和移动对象,第 285 页,第 32 项:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]