如果从函数中返回,本地变量的成员子对象是否也会被移动?

7
C++11标准规定,如果满足复制省略的条件(§12.8/31),则实现必须将作为返回值的本地lvalue变量和函数参数视为rvalue(移动)处理。如果重载分辨率没有成功地详细说明,那么它将被视为lvalue(复制)。
§12.8 [class.copy] p32
当满足复制操作的省略条件,或者只有源对象是函数参数这一事实不成立时,并且要复制的对象由lvalue指定时,首先应像该对象由rvalue指定一样执行选择复制构造函数的重载分辨率。如果重载解析失败,或者所选构造函数的第一个参数类型不是对象类型(可能带cv限定符)的rvalue引用,则再次执行重载分辨率,将对象视为lvalue。注意:无论是否会发生复制省略,都必须执行这个两阶段的重载分辨率。它确定要调用的构造函数,如果未执行省略,则所选构造函数必须是可访问的。 ——结束说明
这是否也包括成员子对象?我用以下代码片段进行了测试:
#include <iostream>

struct traced{
  traced(){ std::cout << "default ctor\n"; }
  traced(traced const&){ std::cout << "copy ctor\n"; }
  traced(traced&&){ std::cout << "move ctor\n"; }
};

struct X{
  traced t;
};

traced f(){
  X x;
  return x.t;
}

int main(){
  traced t = f();
}

在Ideone上的实时示例。而且,无论是GCC 4.7 ToT还是Clang 3.1 ToT都不会显示“move ctor”,这让我相信标准不包括成员子对象。

我有什么遗漏了吗?我的测试代码出了问题吗?到底是什么导致输出结果如此?


我认为你的测试并不能证明它们无法实现。这可能只是表明编译器很难确认是否满足省略的条件,因为只有在编译器能够确认条件有效时,突出显示的部分才是相关的。 - Martin York
我有点困惑。我认为我混淆了返回值优化和复制省略。我稍微改了一下代码,得到了移动,参见此演示。我直接从f()返回x,允许RVO。它使用了traced t = f().t;,演示了移动。(我不知道这是否有帮助!) - Aaron McDaid
@Aaron。RVO只是复制省略的一个应用程序。:)可以说是一个适当的子集。此外,使用返回后的访问的想法很好。不幸的是,在我的实际代码中不适用。 :/ - Xeo
1个回答

6
当返回子对象时,无法省略其构造过程。可以这样理解:移动和复制省略本质上相当于在最终移动或复制到的位置构造对象。对于完整对象,这是有效的,因为将会有适当的空间被设置。但对于子对象而言,则不起作用,因为您将构造封闭对象。即使它与子对象具有相同的大小,即有足够的空间,封闭对象也会被销毁,并可能对子对象产生奇怪的影响。
实际上,这意味着主题的构造不能被省略。

这反过来意味着§12.8/32的整个段落都不适用。我想知道为什么标准不直接说,如果返回的对象是本地lvalue或按值传递的函数参数,则应该移动它,否则复制它。在这里只依赖于复制省略的资格似乎有点严格。有任何原因吗? - Xeo
@Richard:做得好。 :) 如果没有特别的理由禁止,也许你可以在会议上提出我评论中的最后一点? :) - Xeo
Richard与核心工作组的同事们一起,可能会更好地回答这个问题(我是一个库的人,今天坐在进化中,不过)。我猜这些字眼的作用是将一些规则有效地委托给现有的规范:这些东西通常被仔细措辞以避免可能相当微妙的不一致性。此外,你所说的话并不完全适用:据我所知,这些话的想法是找出类型是否可以移动或复制,因为这是可能被省略的先决条件。 - Dietmar Kühl
哦,但是据我理解,在返回语句中移动本地变量是指定为复制省略的。 - Xeo
2
虽然你所说的NRVO可能是正确的,但这并不一定适用于复制到移动处理。将子对象移动而不是复制它并没有技术问题。仅移动子对象而不是整个完整对象在代码中已经是必需的,例如 return X().t; - Johannes Schaub - litb
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接