左值、右值
左值:有变量名、可以出现在等号左边或右边,可以被赋值、可以被取地址,例如:
int a;
a = 5;
int* p = &a;
*p = 10;
右值:没有变量名、不能出现在等号左边,只能出现在等号右边,不能被取地址,例如:
int a = 5;
int b = a;
//5 = b;错误,5是一个字面常量
int c = a + b;
//(a+b) = 20;错误,a+b属于一个临时变量,属于右值
int func(int a,int b)
{
return a + b;
}
//func(1,2) = 10;错误,func(int a ,int b)的返回值是一个临时变量,属于右值
左值引用、右值引用
左值引用:只能对左值进行引用的类型,但是常量左值引用可以对右值进行引用,保证了不会对引用进行修改,例如:
int a = 5;
int& b = a;//correct! b是一个左值引用,引用左值a,即b就是a的别名
int& c = 10;//error! 非常量引用c不能引用一个右值
const int& d = 20;//correct! d是一个常量引用,可以引用一个右值
右值引用:只能对右值进行引用,区别于左值引用,可以利用此特性提供重载,用于移动构造中,例如:
int a&& = 5;//correct!a是一个右值引用
class object{
public:
object(const object& obj)//拷贝构造函数
{
m_data = new int[obj.m_size];
memcpy(m_data,obj.m_data,obj.m_size);
m_size = obj.m_size;
}
object(object&& obj)//移动构造函数
{
m_data = obj.m_data;
obj.m_data = nullptr;
m_size = obj.m_size;
}
private:
int* m_data;
int m_size
};
引用在c++中是一个特别的类型,因为它的值类型和变量类型不一样,引用这个变量是一个引用类型,但是它的值类型不是, 左值引用和右值引用变量的值类型都是左值, 而不是左值引用或者右值引用。
int a = 5;
int &b = a;
int&& c = 10;
b = 15;//correct!,b的值类型是int,不是int&
c = 20;//correct!,c的值类型是int,不是int&&
万能引用
前面说过右值引用只可以引用右值,但是在模板中,T&&并不表示这是一个右值引用,而是一个万能引用,英文叫做Universal References,而且可以接收左值或者右值,例如:
template<typename T>
class object{
object(T&& t);
};
int main()
{
int a = 5;
object<int> obj(a);//correct!
object<int> obj1(5);//correct!
return 0;
}
但是不是所有的模板引用都是万能引用,万能引用仅发生在类型推导时,即已经明确的类型,加上&&就是右值引用,例如:
template<typename T>
class object{
object(T&& obj);//属于万能引用,因为类型需要进行推导
};
template<typename T>
class object{
object(object && obj);//是右值引用,因为类型是明确的
};
万能引用的折叠规则:
T&、T& &&和T&& &都会被推导为左值引用T&;
T&& &&才会被推导为T&&;
std::move
std::move的作用是将一个左值/右值强制转化为右值引用,继而可以通过右值引用使用该值,所以称为移动语义。std::move会将对象的状态或者所有权从一个对象转移到另一个对象,不会有内存的迁移或者内存拷贝,性能会得到改善和提升。
需要注意:传给std::move的参数,在调用之后,就不能再使用了。
函数原型:
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type&&>(t);
}
/// 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; };
可以看出,std::move原型的实现最终就是将传入的所有类型转化为一个右值引用返回。
举个例子,下面分别在拷贝和移动到其他对象之后,原对象的值输出对比:
#include<iostream>
#include<string>
#include <utility>
#include<vector>
int main()
{
std::string str = "hello word";
std::string strLeftRef(str);
std::cout << "str:\"" << str << "\" copy str:" << strLeftRef << std::endl;
std::string strRightRef(std::move(str));
std::cout << "str:\"" << str << "\" move str:" << strRightRef << std::endl;
return 0;
}
输出:
str:"hello word" copy str:hello word
str:"" move str:hello word
std::forward
万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续再去使用这个引用类型的变量时就退化成了左值,例如:
void func(int&& a)
{
}
template<typename T>
class object{
public:
void Func(T&& a)
{
func(a);
}
};
int main()
{
object<int> obj;
obj.Func(1);
return 0;
}
编译时会报错:
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
func(a);
报错原因是:不能绑定右值引用类型"int&&" 到左值类型"int"上,根本原因就是在万能引用传递后,a退化为了int类型。
如果希望能够在传递过程中保持它的左值或者右值的属性, 那么就要使用forward来解决这个问题,下图将forward的原型引入