C++ std::move与编译器优化

C++ std::move与编译器优化,c++,compiler-optimization,move-semantics,C++,Compiler Optimization,Move Semantics,例如: void f(T&& t); // probably making a copy of t void g() { T t; // do something with t f(std::move(t)); // probably something else not using "t" } 在这种情况下,void f(T const&T)是否等效,因为任何好的编译器都会生成相同的代码?如果这很重要,我对>=VC10和>=GCC4.6感兴

例如:

void f(T&& t); // probably making a copy of t

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}
在这种情况下,
void f(T const&T)
是否等效,因为任何好的编译器都会生成相同的代码?如果这很重要,我对>=VC10和>=GCC4.6感兴趣

编辑:

根据答案,我想对这个问题做一点阐述:

比较
rvalue reference
passby value
方法,很容易忘记在
pass by value
中使用
std::move
。编译器是否仍能检查变量是否不再进行更改,并消除不必要的副本


rvalue reference
方法仅使优化版本“隐式”,例如
f(T())
,并要求用户明确指定其他情况,如
f(std::move(T))
或明确复制
f(T(T))如果用户没有使用
t
实例。因此,从优化的角度来看,
rvalue reference
方法被认为是好的吗?

好的,这取决于f对t做了什么,如果它创建了它的副本,那么我甚至会详细说明这一点:

void f(T t) // probably making a copy of t
{
    m_newT = std::move(t); // save it to a member or take the resources if it is a c'tor..
}

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}
然后你允许移动任务优化发生,你在任何情况下都会获取“t”资源,如果它被“移动”到你的函数中,那么你甚至可以获得将它移动到函数中的非副本,如果它没有被移动,那么你可能必须有一个副本

现在,如果在以后的代码中,您将:

f(T());
然后ta da,自由移动优化,f用户甚至不知道

请注意引号:“在这种情况下,void f(T const&T)是否等效,因为任何好的编译器都会生成相同的代码?”


它不是等价的,它是更少的工作,因为只有“指针”被转移,根本不调用任何命令,既不移动也不做任何其他事情,这肯定是不一样的。只有一次,
T&
只能绑定到右值,而
T const&
可以同时绑定到右值和左值。其次,
T const&
不允许任何移动优化。如果您“可能想复制
t
”,则
t&
允许您实际复制
t
,这可能更有效

例如:

void foo(std::string const & s) { std::string local(s); /* ... */ }

int main()
{
    std::string a("hello");
    foo(a);
}
在此代码中,包含
“hello”
的字符串缓冲区必须存在两次,一次在
main
的主体中,另一次在
foo
的主体中。相反,如果使用右值引用和
std::move(a)
,则可以“移动”相同的字符串缓冲区,并且只需要分配和填充一次

正如@Alon所指出的,正确的习语实际上是:


采用
常量
左值引用和右值引用是两件不同的事情

相似之处:

  • 两者都不会导致复制或移动,因为它们都是引用。引用仅引用对象,它不会以任何方式复制/移动对象
差异:

  • const
    lvalue引用将绑定到任何对象(lvalue或rvalue)。右值引用只绑定到非
const右值-更为有限

  • 当函数中的参数为
    const
    lvalue引用时,无法修改该参数。当它是右值引用时,可以修改它(因为它是非常量)

  • 让我们看一些例子:

  • const
    左值参考:
    void f(const T&T)

  • 传递左值:

    T t; f(t);
    
    T t;
    f(t);
    
    这里,
    t
    是一个左值表达式,因为它是对象的名称。一个
    const
    lvalue引用可以绑定到任何东西,因此
    t
    将愉快地通过引用传递。没有复制,没有移动

  • 传递右值:

    f(T());
    
    f(T());
    
    这里,
    T()
    是一个右值表达式,因为它创建了一个临时对象。同样地,
    const
    lvalue引用可以绑定到任何东西,所以这是可以的。没有复制,没有移动

  • 在这两种情况下,函数中的
    t
    是对传入对象的引用。它不能通过引用is
    const
    进行修改

  • 取右值引用:`void f(T&&T)

  • 传递左值:

    T t; f(t);
    
    T t;
    f(t);
    
    这将给您一个编译器错误。右值引用不会绑定到左值

  • 传递右值:

    f(T());
    
    f(T());
    
    这很好,因为右值引用可以绑定到右值。函数中的引用
    t
    将引用由
    t()
    创建的临时对象

  • 现在让我们考虑<代码> STD::Stase。第一件事第一:

    std::move
    实际上不会移动任何东西。这个想法是,你给它一个左值,它会把它变成右值。这就是它的全部功能。现在,如果您的
    f
    采用右值引用,您可以执行以下操作:

    T t;
    f(std::move(t));
    
    这是因为,尽管
    t
    是左值,
    std::move(t)
    是右值。现在右值引用可以绑定到它

    那么,为什么要接受右值引用参数呢?事实上,除了定义移动构造函数和赋值操作符外,您不需要经常这样做。每当您定义一个接受右值引用的函数时,您几乎肯定要给出一个
    const
    lvalue引用重载。它们几乎总是成对出现:

    void f(const T&);
    void f(T&&);
    
    为什么这对函数有用?好的,第一个将在您给它一个左值(或
    const
    rvalue)时被调用,第二个将在您给它一个可修改的rvalue时被调用。接收右值通常意味着您得到了一个临时对象,这是一个好消息,因为这意味着您可以破坏它的内部结构,并基于您知道它不会存在太久的事实执行优化

    所以有了这对函数,当你知道你得到了一个临时对象时,你就可以进行优化了

    T