1. 何为左值?何为右值?
简单的说,左值可以放在等号的左边,右值可以放在等号的右边。
左值可以取地址,右值不能取地址。
1.1 左值举例:
- 变量、函数或数据成员
- 返回左值引用的表达式 如 ++x、x = 1、cout << ' ' int x = 0
1.2 右值举例:
- 返回非引用类型的表达式
如 x++、x + 1 - 除字符串字面量之外的字面量如 42、true
将亡值(xvalue)
- 隐式或显式调用函数的结果,该函数的返回类型是对所返回对象类型的右值引用
#include<string>
#include<iostream>
using namespace std;
void print(string& str)
{cout << "left val: " <<str << endl << endl;
}void print(string&& str)
{cout << "right val: " << str <<endl << endl;
}void print(int&& i)
{cout << "right val: " <<i << endl << endl;
}void print(int& i)
{cout << "left val: " <<i << endl << endl;
}int getVal(){return 666;
}int main(int argc, char *argv[])
{string str1 = "hello 1";print(str1);print("hello 2");print(move(str1));int ab = 88;print(ab);print(++ab);print(ab++);print(getVal());//string& aa = "abc"; // 编译不过,左值引用不可以赋右值const string& bb = "abc"; // 常量左值引用可以赋右值return 0;
}
输出:
1.3 左值引用与右值引用
规则简化如下:
左值引用 {左值}
右值引用 {右值}
常左值引用 {右值}
string f()
{return "bbb";
}int main()
{string &s1 = "asd"; // errorconst string &s2 = "asd"; // okconst string& s3 = f(); // okstring&& a2 = "defg"; // okstring&& a3 = f(); // okreturn 0;
}
1.4 引入右值引用意义
可以延长右值的生命周期,右值的生命周期可以与右值引用变量相同
2. 完美转发
2.1 转发引用
在 T 是模板参数时,T&& 的作用主要是保持值类别进行转发,它有个名字就叫“转发引用”(forwarding reference)。因为既可以是左值引用,也可以是右值引用,它也曾经被叫做“万能引用”(universal reference)。
#include<string>
#include<iostream>
using namespace std;
void print(const string& str)
{cout << "left val: " <<str << endl << endl;
}void print(string&& str)
{cout << "right val: " << str <<endl << endl;
}string ff()
{return "fff";
}template<typename T>
void f(T&& param)
{print(forward<T>(param));
}int main(int argc, char *argv[])
{string str1 = "hello 1";const string& bb = "abc";const string& bb2 = ff();f(str1); // left valuef(bb); // left valuef(bb2); // left valuef("abcd"); // rightvaluef(ff()); // right valuereturn 0;
}
输出:
2.2 完美转发 std::forward
2.1.1 源码解析
- 转发左值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
先通过 remove_reference 获得类型type,定义
_t
为左值引用的左值变量,通过static_cast
进行强制转换
- 转发右值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);
}
不同于转发左值,
_t
为右值引用的左值变量,除此之外中间加了一个断言,表示当不是左值的时候,也就是右值,才进行static_cast
转换。
2.1.2 remove_reference 解析
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };// 特化版本
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
remove_reference
的作用是去除T
中的引用部分,只获取其中的类型部分。无论T
是左值还是右值,最后只获取它的类型部分。
现代C++之万能引用、完美转发、引用折叠 - 知乎 (zhihu.com)