右值引用
无名右值引用
- 无名右值引用是右值。
- 无名右值引用的产生方式:
// v是具名右值引用,返回值为无名右值引用
int && fun(int && v) {
return 0;
}
-
std::move
和std::forward
返回的右值引用为无名右值引用,其原理与上述代码类似。
具名右值引用
- 具名右值引用是左值。
- 具名右值引用只能绑定右值,其定义方式:
// v是具名右值引用,返回值为无名右值引用
int && fun(int && v) {
return 0;
}
int main() {
int x1 = 0;
int & x2 = x1;
int && x3 = 0; // 具名右值引用是左值
int && y1 = x1; // 错误,无法将右值引用绑定到在左值
int && y2 = x2; // 错误,无法将右值引用绑定到在左值
int && y3 = x3; // 错误,无法将右值引用绑定到在左值
int && y4 = fun(0); // fun()返回无名右值引用,是右值
int && y5 = std::move(0); // std::move()返回无名右值引用,是右值
return system("pause");
}
转发型引用
描述
- 转发型引用可以保留被引用值的
const
、&
、&&
属性。 - 转发型引用可以引用任意值,不论是右值、左值、有无
const
等。
定义
方式一
// T &&类型参数t叫转发型引用
template <typename T>
void fun(T && t) {
}
方式二
const int && x = 0;
auto && xx = x; // 推断为const int &,注意x在初始化以后就是const int &类型了
推导规则
const int x1 = 1;
const int & x2 = x1;
const int && x3 = 1;
int x4 = 1;
int & x5 = x4;
int && x6 = 1;
auto && y1 = x1; // 推断为const int &
auto && y2 = x2; // 推断为const int &
auto && y3 = x3; // 推断为const int &
auto && y4 = x4; // 推断为int &
auto && y5 = x5; // 推断为int &
auto && y6 = x6; // 推断为int &
auto && y7 = 0; // 推断为具名int &&
auto && y8 = std::move(0); // 推断为具名int &&
std::move()和std::forward()的区别
-
std::forward()
的必须通过显式模板实参来使用。 -
std::move(int)
和std::forward<int>()
和std::forward<int &&>()
完全无任何区别,都是转换为无名int &&
。 -
std::move(int)
和std::forward<int &>()
的区别是前者依旧转换为int &&
,而后者转换为int &
。 - 因为
std::forward()
的这个特性,它的通常用法是在模板中使用std::forward<T>(t)
,并且T
由T && t
的方式捕获以实现完美转发。
完美转发
简述
完美转发:将类型传递给模板参数时,保留所有实参类型的细节(如const,具名右值引用,无名右值引用、左值引用)。
代码
#include <iostream>
void f(const int & v) {} //fun1
// void f(int & v) { } //fun2
// void f(int && v) { } //fun3
//不完美转发版本一
template <typename F, typename T>
void call1(F f, T t) {
f(t);
}
//不完美转发版本二
template <typename F, typename T>
void call2(F f, T && t) {
f(t);
}
//完美转发
template <typename F, typename T>
void call3(F f, T && t) {
f(std::forward<T>(t));
}
int main() {
const int x1 = 1;
const int & x2 = x1;
const int && x3 = 1;
int x4 = 1;
int & x5 = x4;
int && x6 = 1;
call1(f, x1); //类型参数推断为int,丢失类型细节信息,转发失败。(可以调用fun2,但不应该可以)
call1(f, x2); //类型参数推断为int,丢失类型细节信息,转发失败。(可以调用fun2,但不应该可以)
call1(f, x3); //类型参数推断为int,丢失类型细节信息,转发失败。(可以调用fun2,但不应该可以)
call1(f, x4); //类型参数推断为int,丢失类型细节信息,转发失败。(传值的方式调用fun2,但本应该用传引用的方式)
call1(f, x5); //类型参数推断为int,丢失类型细节信息,转发失败。(传值的方式调用fun2,但本应该用传引用的方式)
call1(f, x6); //类型参数推断为int,丢失类型细节信息,转发失败。(传值的方式调用fun2,但本应该用传引用的方式)
call1(f, 0); //类型参数推断为int,丢失类型细节信息,转发失败。(不可以调用fun3,但本应该可以)
call1(f, std::move(0)); //类型参数推断为int,丢失类型细节信息,转发失败。(不可以调用fun3,但本应该可以)
call2(f, x1); //类型参数推断为const int &,转发时类型为const int &,转发成功。
call2(f, x2); //类型参数推断为const int &,转发时类型为const int &,转发成功。
call2(f, x3); //类型参数推断为const int &,转发时类型为const int &,转发成功。
call2(f, x4); //类型参数推断为int &,转发时类型为int &,转发成功。
call2(f, x5); //类型参数推断为int &,转发时类型为int &,转发成功。
call2(f, x6); //类型参数推断为int &,转发时类型为int &,转发成功。
call2(f, 0); //类型参数推断为具名int &&,丢失类型细节信息,转发失败。(调用fun3会出现无法将右值引用绑定到左值的错误。)
call2(f, std::move(0)); //类型参数推断为具名int &&,丢失类型细节信息,转发失败。(调用fun3会出现无法将右值引用绑定到左值的错误。)
call3(f, x1); //类型参数推断为const int &,转发时std::forward将类型const int &转换为const int &,转发成功。
call3(f, x2); //类型参数推断为const int &,转发时std::forward将类型const int &转换为const int &,转发成功。
call3(f, x3); //类型参数推断为const int &,转发时std::forward将类型const int &转换为const int &,转发成功。
call3(f, x4); //类型参数推断为int &,转发时std::forward将类型int &转换为int &,转发成功。
call3(f, x5); //类型参数推断为int &,转发时std::forward将类型int &转换为int &,转发成功。
call3(f, x6); //类型参数推断为int &,转发时std::forward将类型int &转换为int &,转发成功。
call3(f, 0); //类型参数推断为具名int &&,转发时std::forward将具名int &&转换为无名int &&,转发成功。
call3(f, std::move(0)); //类型参数推断为具名int &&,转发时std::forward将具名int &&转换为无名int &&,转发成功。
return system("pause");
}