如何在不妨碍RVO的情况下确保移动?

How to ensure moving without impeding RVO?

本文关键字:情况下 确保 移动 RVO      更新时间:2023-10-16

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

  1. 它符合复制消除条件,编译器执行 RVO。
  2. 它符合复制省略的条件,编译器不执行 RVO,但随后......
  3. 它符合移动构造函数的使用条件并被移动。
  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.案例,执行 RVO,现场演示,生成的汇编器:

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

2. 示例: 1.案例,RVO执行,现场演示,结果汇编器:

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

3. 示例: 2.案例,符合复制要求,RVO未执行,现场演示,结果汇编:

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

4. 示例: 3.case,不符合复制省略条件(x是一个函数参数),但用于移动,现场演示,生成的汇编程序:

A callee4(A x){
return x; 
}

5. 示例: 4.案例,没有复制或隐式移动(见此SO-post),现场演示,结果汇编程序:

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

6. 示例: 4.案例,没有复制或隐式移动,现场演示,结果汇编:

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

什么时候不能执行 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