右值引用与完美转发

右值引用

左值(l-value - locator value)是指存储在内存中、有明确存储地址(可取地址)的数据;
右值(r -value - read value)是指可以提供数据值的数据(不可取地址)
可以对其取地址(&)就是左值,否则为右值 。所有有名字的变量或对象都是左值,而右值是匿名的。

右值分两种

纯右值:非引用返回的临时变量、运算产生的临时变量、原始字面量和 lambda 等
将亡值:与右值引用相关的,比如,T&& 类型函数的返回值、 std::move 的返回值等。

右值引用就是对一个右值进行引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量将会一直存活下去。

1
2
3
4
5
6
7
8
9
10
11
int && v = 10; //v 是对字面量 10 这个右值的引用
OBJ getObj() { return OBJ(); }
int main()
{
int i1;
int&& i2 = i1; // error 左值不能赋值给右值
OBJ& o = getObj(); // error 左值引用不能接将亡值
OBJ&& o1 = getObj(); //右值引用可以接收将亡值
const OBJ& o2 = getObj(); //会进行构造,常量左值是万能的
return 0;
}
1
2
OBJ(const OBJ& a);
OBJ t = getObj();

t进行拷贝构造,根据将亡值创建、且将亡值会进行析构,效率不高。

1
2
OBJ(OBJ&& a);
OBJ t = getObj();

因为getObj返回的是将亡值,编译器自动进行右值处理,可以理解为添加了个std::move进行类型转换。调用移动构造。

&&在模板中

1
2
template<typename T> 
void f(T&& param)//并不一定表示右值引用,若f(左值)则为左值引用

&&在auto中

  • &&在auto中,auto&& v = 左值,则v表示左值引用。
  • decltype(x)&&,表示一个确切的类型,没有auto灵活。此时用左值赋值会报错。

完美转发

1
2
std::forward<>()   //因为参数为&&时,不知道是左值引用还是右值引用
//所以使用forward帮助传递参数,会按实际情况进行转发

有时候右值会转为左值,左值会转为右值。

1
2
3
4
template<typename T> 
T&& move(T& val) {
return static_cast<T&&>(val); //std::move实际上是个类型转换
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename T1, typename T2, typename T3> 
B(T1&& t1, T2&& t2, T3&& t3) :
a1_{std::forward<T1>(t1)},
a2_{std::forward<T2>(t2)},
a3_{std::forward<T3>(t3)}
{ }

template<typename T,class... U>
std::unique_ptr<T> make_unique1(U&&... u){
//return std::unique_ptr<T>(new T(std::forward<U>(u)...)); //右 左 右
return std::unique_ptr<T>(new T(std::move(u)...)); //右 右 右
}

int main() {
int i = 0;
make_unique1<B>(1,i,2); //右 左 右
}
//无论是T&&、左值引用、右值引用,std::forward都会依照原来的类型完美转发