【问题标题】:How to ensure moving without impeding RVO?如何确保在不妨碍 RVO 的情况下移动?
【发布时间】:2019-04-29 08:52:19
【问题描述】:

当从函数返回对象时,自 C++11 以来可能会发生以下情况之一,假设定义了移动和复制构造函数(另请参见本文末尾的示例):

  1. qualifies for copy-elision 和编译器执行 RVO。
  2. 它符合复制省略的条件,编译器不执行 RVO,但是...
  3. qualifies for the usage of move constructor 被移动了。
  4. 以上都没有,并且使用了复制构造函数。

前 3 种情况的建议是不要使用显式 std::move,因为无论如何都会执行移动并且可能会阻止可能的 RVO,例如,请参见 SO-post

但是,在第 4 种情况下,显式 std::move 会提高性能。但作为一个既不熟悉标准也不流利阅读汇编程序的人,区分情况 1-3 和 4 需要花费大量时间。

因此我的问题是:有没有办法以统一的方式处理所有上述情况,例如:

  1. RVO 不受阻碍(案例 1)
  2. 如果不执行 RVO,则使用移动构造函数(案例 2,3 和 4)
  3. 如果没有移动构造函数,则应使用复制构造函数作为后备。

这里有一些例子,也可以作为测试用例。

所有示例都使用以下帮助程序类定义:

struct A{
    int x;
    A(int x_);
    A(const A& a);
    A(A&& a);
    ~A();
};

1.示例: 1.case,RVO 执行,live-demonstrationresulting assembler

A callee1(){
    A a(0);
    return a;
}

2。示例: 1.case,RVO 执行,live-demonstrationresulting assembler

A callee2(bool which){
    return which? A(0) : A(1);
}

3.示例: 2.case,符合复制省略,RVO 未执行,live-demonstrationresulting assembler

A callee3(bool which){
    A a(0);
    A b(1);
    if(which)
      return a;
    else
      return b; 
}

4.示例: 3.case,不符合复制省略(x 是函数参数),但对于移动,live-demonstrationresulting assembler

A callee4(A x){
    return x; 
}

5.示例: 4.case,没有复制省略或隐式移动(见SO-post),live-demonstrationresulting assembler

A callee5(bool which){
    A a(0);
    A b(1);
    return which ? a : b;
}

6.示例: 4.case,没有复制省略或隐式移动,live-demonstrationresulting assembler

A callee6(){
    std::pair<A,int> x{0,1};
    return x.first;
}

【问题讨论】:

  • 不是一个真正的答案,但 gcc 和 clang 已经开始警告悲观的举动:godbolt.org/z/ZkpDgB

标签: c++ c++11 language-lawyer rvo


【解决方案1】:

什么时候不能进行 RVO?

如果以下任何一种情况适用,编译器(通常)将无法执行 RVO:

  1. 返回哪个局部变量取决于条件(局部变量在条件之前定义,而不是在条件内部)
  2. 您正在返回一个类、联合或结构的成员
  3. 您正在取消引用指针以获取返回值(这包括数组索引)

在情况 1 中,您应该使用 std::move 或使用 if 语句而不是三元运算符编写代码。在情况 2 中,您必须使用 std::move。在第 3 种情况下,您还应该明确使用 std::move

确保搬家施工的政策(如果可能)

处理案例1。我们可以通过依赖if返回局部变量的语句来确保值被移动,而不是三元运算符:

A whichOne(bool which) {
    A a(0); 
    A b(1); 
    if(which) {
        return a;
    } else { 
        return b; 
    }
}

如果您真的更喜欢使用三元运算符,请在两个条件上显式使用std::move

A whichOne(bool which) {
    A a(0); 
    A b(1); 
    return which ? std::move(a) : std::move(b); 
}

如果我只想移动a,而不是b,该怎么办?我们可以通过在条件中显式构造它来处理这种情况。在这种情况下,构造的值保证经过 RVO,因为三元的两边都会产生一个 prvalue

A whichOne(bool which) {
    A a(0);
    A b(1); 
    return which 
      ? A(std::move(a)) // prvalue move-constructed from a
      : A(b);           // prvalue copy-constructed from b
}

处理案例 2 和 3。 这非常简单:在返回对象的成员或返回来自数组或指针的引用时使用 std::move

【讨论】:

  • 我的问题不是这些特殊的例子——更多的是一种统一的方式,所以不必知道所有细节。
  • 我想我的信息是“如果你真的,真的需要,只用 move 注释”?我讨论的案例是边缘案例,正是因为语言无法判断程序员的意图。您可以拥有一个良好的编码标准,以便始终使用移动(这就是我的答案试图提供的),但正是由于这些情况下固有的模糊性,您必须将它们与自动发生移动或 RVO 的情况区别对待。
  • 话虽如此,该规则的例外情况都相对不常见,并且很清楚何时发生。返回数组的元素或类的成员看起来与仅返回局部变量不同。返回三元的结果 看起来与返回值不同。你不必知道函数的其余部分的细节来应用我的规则;你只需要看看return语句
  • 类、联合或结构的成员”在技术上都称为“类”。请参阅语法中的class-key
猜你喜欢
  • 2014-10-25
  • 1970-01-01
  • 2016-02-23
  • 1970-01-01
  • 2014-11-17
  • 2021-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多