左值与右值
左值是这样一种表达式,它指向一块物理内存,并允许我们通过运算符&来取得这块内存的地址,而右值则是非左值的表达式。常量和匿名的临时变量都为右值,如函数返回值。
比如:int a=0; //a为左值
string s="ss";//s为左值,"ss"为右值
string ss=string("e");//ss为左值,string("e")为右值
左值引用与右值引用
右值引用
和左值引用
都是引用,都是一个变量(即都是一个左值),左值引用通过在类型名后加 &
来表示,而右值引用则通过在类型名后加 &&
表示。只不过左值引用引用的是左值,而右值引用只能引用右值。函数形参都是左值,因为函数形参都有名称,都可以对形参进行取地址操作。
右值与右值引用
右值不是左值,右值引用是左值,是对右值的一种引用。c++11引入右值引用主要是为了实现移动语义和完美转发。右值引用本身没什么意义。
std::forward与std::move
std::forward实现
template<class TPYE>
TPYE&& forward(typename remove_reference<TPYE>::type& a) noexcept
{
return static_cast<TPYE&&>(a);
}
先看remove_reference的实现,顾明思议就是去掉引用:
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
std::forward实现了完美转发看下面的代码(参考链接:https://blog.youkuaiyun.com/victorydh123/article/details/73603830):
void f(int&& a) {
std::cout << "rvalue" << std::endl;
}
void f(int& a) {
std::cout << "lvalue" << std::endl;
}
template<typename T>
void test(T&& a) {
f(forward<T>(a));//如果没有forward 将永远调用左值版本,因为右值引用是左值。右值引用的形参只能接受右值。完美转发实现了传递进来什么类型,我就返回什么类型。其实就是解决了右值引用形参传入后,形参本身已经是一个左值了,不能作为下一个函数的右值引用参数。
}
template<typename T>
T&& forward(std::remove_reference_t<T>& arg)
{
return static_cast<T&&>(arg);
}
int main()
{
int a = 10;
test(a);//"lvalue"
test(std::move(a));//"rvalue"
//system("pause"); //for windows VS to pause to see the output
return 0;
}
模板推断时(请百度通用引用),引用的折叠规则:
& & -> &
& && -> &
&& & -> &
&& && -> &&
当参数是一个左值时,在通用引用模板类型推断的时候,左值会被推断成左值引用,不管传递的参数是左值类型还是左值引用,通通变成左值引用。
std::move的实现代码也很简单
template<typename _Tp>
inline typename std::remove_reference<_Tp>::type&& move(_Tp&& __t)
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
就是将__t转为右值引用返回。
看上去move什么也没做,没错std::move并不产生新的对象,不会调用构造函数,只是做了一个类型转换,这样做的目的主要是为了支持移动构造和移动赋值,因为移动构造和移动赋值的参数类型为右值引用。
要了解移动构造和移动赋值请百度“移动构造和移动赋值”或者参考这篇博客:https://www.cnblogs.com/ldlchina/p/6608154.html