为什么std :: move会阻止RVO? [英] Why does std::move prevent RVO?

查看:211
本文介绍了为什么std :: move会阻止RVO?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在许多情况下,从函数返回一个局部,RVO踢。但是,我认为显式使用 std :: move 将至少强制移动当RVO不会发生,但RVO仍然应用,如果可能的话。但是,似乎并非如此。

  #includeiostream

class HeavyWeight
{
public:
HeavyWeight()
{
std :: cout< ctor<< std :: endl;
}

HeavyWeight(const HeavyWeight& other)
{
std :: cout< copy<< std :: endl;
}

HeavyWeight(HeavyWeight&&& other)
{
std :: cout< move<< std :: endl;
}
};

HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return heavy;
}

int main()
{
auto heavy = MakeHeavy();
return 0;
}

我用VC ++ 11和GCC 4.71测试了这个代码, ( -O2 )config。不会调用copy ctor。 move ctor仅在调试配置中由VC ++ 11调用。实际上,一切似乎都很好,特别是这些编译器,但据我所知,RVO是可选的。



但是,如果我明确使用 move

  HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return std :: move(heavy);
}

始终调用move ctor。



我的问题是:

- 为什么 std ::移动阻止RVO?

- 什么时候更好的希望最好,并依靠RVO,什么时候应该明确使用 std :: move ?或者,换句话说,如何让编译器优化执行它的工作,并仍然强制移动如果RVO不应用?

解决方案

允许复制和移动检测的情况在标准(版本N3690)的第12.8节§31中找到:


即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现省略类对象的复制/移动构造。在这种情况下,实现将省略的复制/移动操作的源和目标视为简单的两种不同的引用同一对象的方式,并且该对象的销毁发生在当两个对象将被销毁没有优化。在以下情况下(可能会合并以消除多个副本),允许复制/移动操作(称为复制精确)的删除:




  • 在一个 return 语句中使用类返回类型的函数,当表达式是非易失性自动对象的名称一个函数或catch子句参数)具有与函数返回类型相同的cv非限定类型,可以通过将自动对象直接构造为函数的返回值来省略复制/移动操作


  • <
  • [...]


(我抛弃的两种情况指的是抛出和捕获异常对象的情况,我认为对优化不太重要。)



因此,在返回语句中,只有当表达式局部变量的名称时,才会发生复制elision。如果写 std :: move(var),那么它不再是变量的名称。因此,编译器不能移除移动,如果它应该符合标准。



Stephan T. Lavavej在 Going Native 2013 ,并解释了你的情况,以及为什么避免 std :: move()这里。在分钟38:04开始观看。基本上,当返回返回类型的局部变量时,它通常被视为右值,因此默认允许移动。


In many cases when returning a local from a function, RVO kicks in. However, I thought that explicitly using std::move would at least enforce moving when RVO does not happen, but that RVO is still applied when possible. However, it seems that this is not the case.

#include "iostream"

class HeavyWeight
{
public:
    HeavyWeight()
    {
        std::cout << "ctor" << std::endl;
    }

    HeavyWeight(const HeavyWeight& other)
    {
        std::cout << "copy" << std::endl;
    }

    HeavyWeight(HeavyWeight&& other)
    {
        std::cout << "move" << std::endl;
    }
};

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return heavy;
}

int main()
{
    auto heavy = MakeHeavy();
    return 0;
}

I tested this code with VC++11 and GCC 4.71, debug and release (-O2) config. The copy ctor is never called. The move ctor is only called by VC++11 in debug config. Actually, everything seems to be fine with these compilers in particular, but to my knowledge, RVO is optional.

However, if I explicitly use move:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return std::move(heavy);
}

the move ctor is always called. So trying to make it "safe" makes it worse.

My questions are:
- Why does std::move prevent RVO?
- When is it better to "hope for the best" and rely on RVO, and when should I explicitly use std::move? Or, in other words, how can I let the compiler optimization do its work and still enforce move if RVO is not applied?

解决方案

The cases where copy and move elision is allowed is found in section 12.8 §31 of the Standard (version N3690):

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • [...]

(The two cases I left out refer to the case of throwing and catching exception objects which I consider less important for optimization.)

Hence in a return statement copy elision can only occur, if the expression is the name of a local variable. If you write std::move(var), then it is not the name of a variable anymore. Therefore the compiler cannot elide the move, if it should conform to the standard.

Stephan T. Lavavej talked about this at Going Native 2013 and explained exactly your situation and why to avoid std::move() here. Start watching at minute 38:04. Basically, when returning a local variable of the return type then it is usually treated as an rvalue hence enabling move by default.

这篇关于为什么std :: move会阻止RVO?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆