1、左值、右值概念
左值:
是指在内存中有明确的地址,可以进行取地址操作。
// 以下的a、p、*p、b都是左值
int a = 3;
int* p = &a;
*p;
const int b = 2;
右值:
只提供数据,无法进行取地址操作。
double x = 1.3, y = 3.8;
// 以下几个都是常见的右值
10; // 字面常量
x + y; // 表达式返回值
fmin(x, y); // 传值返回函数的返回值
注意:
所有有名字的都是左值,而右值是匿名的。
一般情况下,位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况。
2、左值引用、右值引用
左值引用:
需要引用一个有名字的变量,不能引用不具名的对象。
// 以下几个是对上面左值的左值引用
int& ra = a;
int*& rp = p;
int& r = *p;
const int& rb = b;
右值引用:
只能引用字面常量,不具有名字。
C++11新引入了右值引用,记作“&&”。
// 以下几个是对上面右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
注意:
左值引用:
(1)左值引用只能引用左值,不能直接引用右值。
(2)但是const左值引用
既可以引用左值,也可以引用右值。
左值引用使用场景:
(1)做函数参数。
(2)做函数返回值。【当返回对象出了函数作用域以后仍然存在时,可以使用左值引用返回;当返回对象(对象是函数内的局部对象)出了函数作用域以后不存在时,不能使用左值引用。】
右值引用:
(1)右值引用只能引用右值,不能直接引用左值。
(2)但是右值引用可以引用被move
的左值。
右值引用使用场景:
(1)移动语义。【std::move()】。
(2)C++11标准的STL 容器的相关接口函数也增加了右值引用版本。【vectoe::push_back、vector::insert、vector::operator=、map::insert、list::push_back】。
(3)完美转发。
代码:
#include <iostream>int main()
{int x = 10;int y = 20;/**************************** 左值引用 ****************************/// 左值引用只能引用左值int t = 8; // t是左值int& t1 = t; // t1是左值//int& t2 = 8; // 编译报错,因为8是右值,不能直接引用右值// const左值引用既可以引用左值const int& t3 = t; // t3是左值,t也是左值const int& t4 = 8; // t4是左值,也可以引用右值const double& r1 = x + y;/**************************** 右值引用 ****************************/// 右值引用只能引用右值int&& r1 = 10;double&& r2 = x + y;const double&& r3 = x + y;int t = 10;//int&& r4 = t; // 编译报错,不能直接引用左值int *p = &t;// 右值引用可以引用被move的左值int&& r5 = std::move(t);int*&& r6 = std::move(p);int&& r7 = std::move(*p);return 0;
}
结果:
3、引用折叠
C++中,并不是所有情况下 && 都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数,想要指定为 T&&,如果是自动类型推导,需要指定为 auto&&,这两种情况下,&& 被称作“未定的引用类型”。
另外 const T&& 表示一个右值引用,不是未定引用类型。
template<typename T>
void fun(T&& param)
{work(forward<T>param)
}
int main()
{fun(10); //对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用int x = 1;fun(x); //对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用return 0;
}
因为 T&& 或者 auto&& 这种未定引用类型作为参数时,有可能被推导成右值引用,也有可能被推导为左值引用,在进行类型推导时,右值引用会发生变化,这种变化被称作引用折叠。折叠规则如下:
(1)提供右值推导 T&& 或者 auto&& 得到的是一个右值引用类型,const T&& 表示一个右值引用。
(2)通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 绘制 auto&& 得到的是一个左值引用类型。
int main()
{int&& a1 = 1; //右值 ---- 右值引用auto&& bb = a1; //右值引用 ---- 左值引用auto&& bb1 = 2; //右值 ---- 右值引用int a2 = 1;int& a3 = a2; //左值 ---- 左值引用auto&& cc = a3; //左值引用 ---- 左值引用auto&& cc1 = a2; //左值 ---- 左值引用const int& s1 = 1; //常量左值引用const int&& s2 = 1; //常量右值引用auto&& dd = s1; //常量左值引用 ---- 左值引用auto&& ee = s2; //常量右值引用 ---- 左值引用return 0;}
4、std::forward()
完美转发:
一个函数或类模板可以将其参数原封不动地转发给另一个函数或类模板,同时保持被转发参数的左右值特性(lvalue 或 rvalue)。
std::forward:
C++11 引入的模板函数,其主要作用是在模板函数或模板类中,将一个参数以“原样”的方式转发给另一个函数,该函数被用于实现完美转发。
在 C++ 中,函数参数可以是左值引用,也可以是右值引用。对于一个模板函数或类模板,当传递一个参数时,如果该参数是左值,那么传递的就是一个左值引用;如果该参数是右值,那么传递的就是一个右值引用。
代码:
#include <iostream>void func(int& x) {std::cout << "lvalue reference: " << x << std::endl;
}void func(int&& x) {std::cout << "rvalue reference: " << x << std::endl;
}// 模板函数
template<typename T>
void Box(T&& arg) {func(std::forward<T>(arg));
}int main() {int x = 42;// 调用模板函数,传入参数x,是一个左值;调用了左值的打印函数;Box(x);// 调用模板函数,传入参数1,是一个右值;调用了右值的打印函数;Box(1);return 0;
}
结果:
lvalue reference: 42
rvalue reference: 1