将局部变量移入不同类型的返回值

alt*_*gel 7 c++ move language-lawyer

从C ++中的函数返回值时,我们具有复制省略和(命名)返回值优化,可帮助我们创建更有效的代码。简而言之,下面的代码:

std::vector<int> make_vec_1(){
    std::vector<int> v;
    v.resize(1e6);
    return v;
}
Run Code Online (Sandbox Code Playgroud)

导致无提示的移动或直接构造到返回值的目标位置,而不是副本。围绕此的规则还意味着,在返回时显式移动返回的对象实际上会阻止这些优化。

std::vector<int> make_vec_2(){
    std::vector<int> v;
    v.resize(1e6);
    return std::move(v); // BAD
}
Run Code Online (Sandbox Code Playgroud)

如Scott Meyers的有效现代C ++项目25中所述,此版本可防止RVO 。


我的问题是,当返回类型不同但可以从一个或多个局部变量进行移动构造时会发生什么?考虑以下每个返回可选向量的函数:

std::optional<std::vector<int>> make_opt_vec_1(){
    std::vector<int> v;
    v.resize(1e6);
    return v; // no move
}

std::optional<std::vector<int>> make_opt_vec_2(){
    std::vector<int> v;
    v.resize(1e6);
    return std::move(v); // move
}
Run Code Online (Sandbox Code Playgroud)

以下哪项是正确的?return std::move(v)起初,这行对我来说就像是一个危险信号,但我也怀疑这是正确的做法。以下两个返回一对向量的函数也是如此:

std::pair<std::vector<int>, std::vector<int>> make_vec_pair_1(){
    std::vector<int> v1, v2;
    v1.resize(1e6);
    v2.resize(1e6);
    return {v1, v2}; // no move
}

std::pair<std::vector<int>, std::vector<int>> make_vec_pair_2(){
    std::vector<int> v1, v2;
    v1.resize(1e6);
    v2.resize(1e6);
    return {std::move(v1), std::move(v2)}; // move
}
Run Code Online (Sandbox Code Playgroud)

同样,在这种情况下,尽管乍看之下很奇怪,但我认为进入回报值是更好的选择。

我是否正确,当类型不同时最好移入返回值,但是可以从要移出的局部变量构造移回值?我是否误解了NRVO,还是在这里还有其他一些优化措施?

Sto*_*ica 9

我是否正确,当类型不同时最好移入返回值,但是可以从要移出的局部变量构造移回值?我是否误解了NRVO,还是在这里还有其他一些优化措施?

您确实错过了一件事。即使类型不同,也会自动执行隐式移动。

[class.copy.elision](重点是我的)

3在以下复制初始化上下文中,可以使用移动操作代替复制操作:

  • 如果return语句中的表达式是一个(可能带有括号的)id表达式,该对象使用在最里面的封闭函数或lambda表达式的主体或参数声明子句中声明的具有自动存储期限的对象进行命名,或者

  • 如果throw-expression的操作数是非易失性自动对象(函数或catch子句参数除外)的名称,其范围不会超出最里面的try块的末尾(如果有) ,

首先执行重载决议以选择副本的构造函数,就好像该对象由rvalue指定一样。如果第一个重载解析失败或没有执行,或者如果所选构造函数的第一个参数的类型不是对该对象类型的右值引用(可能是cv限定),则再次执行重载解析,将对象视为左值。[注:无论是否发生复制删除,都必须执行此两阶段重载解决方案。如果不执行省略操作,它将确定要调用的构造函数,并且即使取消了调用,所选的构造函数也必须可访问。?—尾注?]

这不是取决于类型匹配,而是在不发生全(N)RVO的情况下的后备行为。因此,通过显式移入不会获得任何收益make_opt_vec_2

考虑到这std::move要么是悲观的,要么是完全多余的,所以我认为最好在简单地返回函数局部对象时要这样做。

您想要显式编写移动的唯一情况是返回的表达式更复杂时。在那种情况下,您确实是一个人,不动则是潜在的悲观情绪。因此make_vec_pair_2,进入配对正确的做法。

经验法则是不要仅移动作为函数本地对象的id表达式。否则,请移开。