关于右值的定义可以看我的这篇文章。
std::move与std::forward的一些知识参考文章里的说法:
要认识它的作用,需要知道C++中的几条规则:(这里有篇挺好的文章:http://blog.csdn.net/zwvista/article/details/6848582,但似乎因标准的更新,其中的规则已不完全成立了)
1.引用折叠规则:
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&
2.对于模板函数中的形参声明T&&(这里的模板参数T,最终推演的结果可能不是一个纯类型,它可能还会带有引用/常量修饰符,如,T推演为const int时,实际形参为const int &&),会有如下规则:
如果调用函数时的实参为U&(这里的U可能有const/volatile修饰,但没有左/右引用修饰了),那么T推演为U&,显然根据上面的引用折叠规则,U& &&=>U&。
如果调用实参为U&&,虽然将T推导为U&&和U都能满足折叠规则(U&& &&=> U&&且U &&=>U&&),但标准规定,这里选择将T推演为U而非U&&。
总结一下第2条规则:当形参声明为T&&时,对于实参U&,T被推演为U&;当实参是U&&时,T被推演为U。当然,T和U具有相同的const/volatile属性。
3.这点很重要,也是上面zwvista的文章中没有提到的:形参T&& t中的变量t,始终是左值引用,即使调用函数的实参是右值引用也不例外。可以这么理解,本来,左值和右值概念的本质区别就是,左值是用户显示声明或分配内 存的变量,能够直接用变量名访问,而右值主要是临时变量。当一个临时变量传入形参为T&& t的模板函数时,T被推演为U,参数t所引用的临时变量因为开始能够被据名访问了,所以它变成了左值。这也就是std::forward存在的原因!当你 以为实参是右值所以t也应该是右值时,它跟你开了个玩笑,它是左值!如果你要进一步调用的函数会根据左右值引用性来进行不同操作,那么你在将t传给其他函 数时,应该先用std::forward恢复t的本来引用性,恢复的依据是模板参数T的推演结果。虽然t的右值引用行会退化,变成左值引用,但根据实参的 左右引用性不同,T会被分别推演为U&和U,这就是依据!因此传给std::forward的两个参数一个都不能 少:std::forward(t)。
通过上面这段描述,能知道当传入f(T && a)的T是int&&时,a将会是一个左值,但T的类型仍然是int &&,看下面两个例子。
std::move基本等同于一个类型转换:static_cast<T&&>(
lvalue
)
首先
第一个例子,说明 a将会是一个左值 :
#include <iostream>
#include <typeinfo>
void printValType(int &&val)
{
std::cout << "int &&" << std::endl;
}
template<typename T>
void f(T &&a)
{
printValType(a);
// printValType(std::forward<T>(a));
}
int main()
{
int a = 1;
f(std::move(a));
// f(a);
}
编译将报错,报错明确指出,printValType(a); 中的a是左值,如下:
第二个例子,说明 T将仍然是int &&类型 :
注:关于是std::forward< T >(t)的用法,简单来说返回T的类型并使变量 t 转化为该类型,相当于以下语法:
std::forward< T >(t) == static_cast< T >(t)
详细的内容可以看开头的文章。
#include <iostream>
#include <typeinfo>
void printValType(int &&val)
{
std::cout << "int &&" << std::endl;
}
template<typename T>
void f(T &&a)
{
// printValType(a);
printValType((T)a);
printValType((T&&)a);
printValType(static_cast<T&&>(a));
printValType(static_cast<T>(a));
printValType(std::forward<T>(a));
printValType(std::forward<T&&>(a));
}
int main()
{
int a = 1;
f(std::move(a));
// f(a);
}
结果输出如下: