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
是对传入对象的引用。它不能通过引用isconst
进行修改
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