在了解二者之前需要先了解一下左值右值的概念。这是学习二者的基础。
1、左值与右值的概念
引用c语言中的概念:左值既可以出现在等号左边又可以出现在等号右边,而右值则只能出现在等号右边,这就是二者的区别。
左值的特点:可以寻址的变量,可持久性。
右值的特点:不能寻址的常量,或者是表达式求值过程中创建的无名临时对象、或者短暂性的。
2、左值引用与右值引用
左值引用:引用的是一个对象
右值引用:必须绑定到右值的引用,可实现“移动语义”,通过"&&"获取右值引用。
int x = 5; //x为左值
int &y = x; //y为左值引用
int &&z = x * 6; //z为右值引用, 因为当x*6 在等号左边的时候表达式不成立
int & z1 = x * 6; //错误,因为x * 6 是一个右值
const int &z2 = x * 6; //正确,可以将一个const引用绑定到一个右值上。
3、二者区别
std::move()
参数传入右值时候是无条件转换,而std::forword()
参数传入右值的时候是有条件转换。如果传入的值都是右值二者是没有区别的。
4、引用折叠规则
如果间接的创建一个引用的引用,这个时候引用就会被折叠一个普通的左值引用类型,有一种情况除外,右值引用的右值引用会被折叠成一个右值引用,即T && &&
会被折叠成T &&
。
- T& &,T&& & ,T& && 会被折叠成 T&
- T&& && 会被折叠成T&&
5、右值引用推断规则
当一个左值传递给一个参数为右值引用的函数,并且该函数是一个函数函数模板,根据传入的值来推断模板参数类型。
template <typename T>
void func(T &&);
int i = 10;
f(i);
//推断结果 T类型为int& 而不是 int
因为接受的是i,而i是可以正常传入func中的,所以T && 必须是一个左值的引用,
根据折叠规则,T 只有为int &的时候才能折叠成 T&的样子,所以T 推断类型为int &.
6、std::move()
为什么这个函数可以将一个左值转换成一个右值呢?先看一下标准库中函数定义:
template<typename T>
typename remove_reference<T>::type && move(T&& t)
{
return static_cast<typename remove_reference<T>::type &&>(t);
}
很明显这需要用到右值引用的推断规则。
- std::move(string(“world”)); //可以看出
string("world")
是一个右值,因为是一个匿名对象。
1、 根据右值引用推断规则可以知道,要想传递一个右值接收的参数必须是一个右值引用,所以T 的类型为string
。
2、 move()的参数类型为string&&
3、static_cast<typename remove_reference<string>::type &&>(t)
转变为static_cast<string &&>(t)
因为t的类型为string &&
而强转的类型也是,所以什么操作都不做,返回string&&
。 string s("world"); std::move(s)
可以看出传入的值s
是一个左值。
1、模板参数要能接收左值必须要用左值的引用才能接收,根据折叠规则可以推到出T
类型为string &
这样的话,才能使T&&
转变为string &
。
2、move()
参数参数类型为string &
.
3、static_cast<typename remove_reference<string>::type &&>(t)
转变为static_cast<string &&>(t)
因为t的类型为string &
会被强制转化为string &&
类型。
由此可以看出std::move()能够接收左值和右值背后都是static_cast()
在搞鬼。
这就是std::move()能无条件转换的原因。
void func(int&& a)
{
cout << a << endl;
}
int a = 6;
func(std::move(a));
int b = 10;
func(static_cast<int&&>(b));
//以上二者的看似不一样实则一样。
7、std::forword()
源码
1、
template<class T> inline
constexpr T&& forward(typename remove_reference<T>::type& _Arg)
{
return (static_cast<T&&>(_Arg));
}
2、
template<class T> inline
constexpr T&& forward(typename remove_reference<T>::type&& _Arg)
{
return (static_cast<T&&>(_Arg));
}
举例分析
class Foo
{
public:
std::string member;
template<typename T>
Foo(T&& member): member{std::forward<T>(member)} {}
};
传入一个左值
1、当传入的参数是一个左值,根据以上推到,可知T = string &
。
2、std::forward<T>(member)
转化为std::forward<string &>(member)
3、传入的值是一个左值,返回的是一个左值引用。
传入一个右值
1、根据以上推到,可知T = string
.
2、std::forward<T>(member)
转化为std::forward<string>(member)
3、传入一个右值,返回的也是一个右值。
8、二者对比
std:move()
传入的参数无论是左值还有右值最终都会被转换为右值。内部主要是static_cast<>()
做了强转操作。std::forword()
当传入左值的时候返回的是左值,传入右值的时候返回的是右值,不会改变值的属性。
9、例子
#include <iostream>
#include <memory>
using namespace std;
struct tar_str
{
tar_str(int && n)
{
cout <<"rval n = "<< n <<endl;
}
tar_str(int & n)
{
cout <<"lval n = "<< n << endl;
}
};
class testB
{
public:
template<class T1,class T2,class T3>
testB(T1 && t1,T2&& t2,T3 && t3):
m_a1(std::forward<T1>(t1)),
m_a2(std::forward<T2>(t2)),
m_a3(std::forward<T3>(t3))
{}
private:
tar_str m_a1, m_a2, m_a3;
};
template<class T,class U>
std::unique_ptr<T> make_unique1(U && u) //(U && u) 表示支持左值和右值传参
{
return std::unique_ptr<T>(new T(std::forward<U>(u)));
//return std::unique_ptr<T>(new T(std::move(u))); //把所有的都改成右值
}
template<class T, class ...U>
std::unique_ptr<T> make_unique2(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()
{
auto p1 = make_unique1<tar_str>(2);
int i = 10;
auto p2 = make_unique1<tar_str>(i);
cout <<"---------------------------"<<endl;
int j = 100;
auto p3 = make_unique2<testB>(2, i, j);
return 0;
}
使用return std::unique_ptr<T>(new T(std::forward<U>(u)))
运行结果
使用return std::unique_ptr<T>(new T(std::move(u)))
运行结果