一、使用模板,可以实现左值引用和右值引用,当实参可能会左值,也可能为右值时使用引用结合模板。
二、两个重要工具
1、move:实现移动语义,将左值变为右值。使用语法:move(左值)。
2、forward:可以保持数据的类型不变,可用于实现完美传参和完美转发。
2.1、为啥需要forward:因为在C++函数调用时,实参变量都会变为左值,而实参本身可能不是左值,这就导致实参失去了原来的数据类型。
实参变量:左值变量,左值引用类型变量,右值引用类型变量,都有内存实体,能取地址;
非变量:右值,没有内存实体,在寄存器里,不能取地址。
2.2、完美转发:模板将自身的形参作为其他函数的实参时,保持模板形参的数据类型不变,不让模板的形参在作为其他函数的实参时全部变为左值。即实参类型为左值或左值引用时就传递左值或左值引用,实参类型为右值引用或右值时就传递右值或右值引用。
#include<iostream>
using namespace std;
void display(int& val) { cout << "& call" << endl; }
void display(int&& val) { cout << "&& call" << endl; }
void display(const int& val) { cout << "cons& call" << endl; }
void display(const int&& val) { cout << "const&& call" << endl; }
//引用+模板
template<typename T>
void func(T&& t) {
//display(t); //t变为左值,只会调用形参为int&
display(forward<T>(t)); //forward实现完美转发
}
void testLeft() {
int a(10);
cout << "左值推理:";
func(a);
int& ra(a);
cout << "左值引用推理:";
func(forward<int&>(ra));
const int b(200);
cout << "const左值推理:";
func(b);
const int& rb(b);
cout << "const左值引用推理:";
func(forward<const int&>(rb));
}
void testRight() {
cout << "右值推理:";
func(10000); //不是变量,实参不会变为左值
cout << "右值引用推理:";
int&& rr(1000000);
//forward<变量原有类型> (变量名)
func(forward<int&&>(rr)); //保持数据类型为int&&,而不变为int
const int a(1);
cout << "const 右值引用推理:";
//move(左值):将左值变为右值
const int&& rra(move(a));
func(forward<const int&&>(rra));
}
int main() {
testLeft();
cout << "--------华丽的分割线-----------" << endl;
testRight();
return 0;
}
三、推理规则:
/*
设左值类型为A,左值引用类型A&,右值引用类型A&&
1.传入左值A时:模板 T -> A& ,形参类型 t -> A&
2.传入左值引用A&时:模板 T -> A& ,形参类型 t -> A&
3.传入右值时:模板 T -> A, 形参类型 t -> A&&
4.传入右值引用A&&时:模板 T -> A, 形参类型 t -> A&&
*/
四、规律总结
/*
结论:
1.左值和左值引用做实参时,模板里的T为左值引用类型,形参类型T&&也为左值引用类型
2.右值和右值引用做实参时,模板里的T为左值类型,形参类型T&&为右值引用类型
*/
四、forward图示补充
由上图知,不使用forward来保持参数的原有数据类型就会和左值调用一样。
使用forward之后,实参就保持了原有的数据类型不变,实现完美传参。