左值、右值、左值引用和右值引用

左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。  

右值不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。

左值引用:绑定到左值的引用,通过&来获得左值引用。非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。

右值引用:C++11中新增加的一个很重要的特性,他主是要用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。通过引入右值引用,很好的解决了这两个问题,改进了程序性能.

从4行代码看右值引用 - qicosmos(江南) - 博客园从4行代码看右值引用 概述 右值引用的概念有些读者可能会感到陌生,其实他和C++98/03中的左值引用有些类似,例如,c++98/03中的左值引用是这样的: int i = 0; int& jhttps://www.cnblogs.com/qicosmos/p/4283455.html

T&& k = getVar();

getVar()产生的临时值在表达式结束之后不会被自动销毁,他的生命周期将会通过右值引用得以延续,和变量k的生命周期一样长。

void processValue(int& a) { cout << "lvalue" << endl; }
void processValue(int&& a) { cout << "rvalue" << endl; }
void processValue(const int& a) { cout << "const lvalue" << endl; }

template 
void forwardValue(T& val)
{
	processValue(val); //右值参数会变成左值 
}
template 
void forwardValue(const T& val)
{
	processValue(val); //参数都变成常量左值引用了 
}

int main()
{
	int i = 0;
	forwardValue(i); //传入左值 
	forwardValue(0);//传入右值 
	return 0;
}

输出:
lvalue
const lvalue
void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template 
void forwardValue(T&& val)
{
    processValue(val); 
}
void Testdelcl()
{
    int i = 0;
    forwardValue(i); //传入左值 
    forwardValue(0);//传入右值 
    int&& t = 0;
	processValue(t);
    processValue(0);
}
输出:
lvaue 
lvalue  因为T&& val是一个右值引用,但是val本身是个左值,因此调用的是lvalue函数。
lvalue  因为t是一个右值引用,但是t本身是个左值,传递给processValue的是一个左值引用
        因此调用的是lvalue函数。
rvalue  传递给processValue的是一个右值引用

std::forward:C++11引入了完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中的std::forward正是做这个事情的,他会按照参数的实际类型进行转发。

void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template 
void forwardValue(T&& val)
{
    processValue(std::forward(val)); //照参数本来的类型进行转发。
}
void Testdelcl()
{
    int i = 0;
    forwardValue(i); //传入左值 
    forwardValue(0);//传入右值 
}
输出:
lvaue 
rvalue

universal references:右值引用T&&是一个universal references,可以接受左值或者右值,正是这个特性让他适合作为一个参数的路由,然后再通过std::forward按照参数的实际类型去匹配对应的重载函数,最终实现完美转发。

当T为左值引用,类似(int&),(int& &&)根据引用折叠,等于(int&)(左值引用);

当T为右值引用,类似(int&&),(int&& &&)根据引用折叠,等于(int&&)(右值引用)

 引用折叠

左值、右值、左值引用和右值引用_第1张图片

std::move

//原始的,最通用的版本
template  
struct remove_reference 
{
	typedef T type;
};
//部分版本特例化,将用于左值引用和右值引用
template  struct remove_reference //左值引用
{
	typedef T type;
};

template  struct remove_reference //右值引用
{
	typedef T type;
};

int main()
{
	
	int a = 0;
	remove_reference::type t1; //通用版本
	remove_reference::type t2; //通用版本
	int& b = a;
	remove_reference::type t3; //左值引用版本
	int&& c = 0;
	remove_reference::type t4; //右值引用版本

	return 0;
}
template 
typename remove_reference::type&& move(T&& t)
{
return static_cast::type &&>(t);
};


int var = 10; 

转化过程:
1. std::move(var) => std::move(int&& &) => 折叠后 std::move(int&)

2. 此时:T 的类型为 int&,typename remove_reference::type 为 int
,这里使用 remove_reference 的左值引用的特例化版本

3. 通过 static_cast 将 int& 强制转换为 int&&

整个std::move被实例化如下
int&& move(int& t) 
{
    return static_cast(t); 
}

Tips:

push_back中代表的是右值,emplace_back代表的是universal reference。

emplace_back的执行效率比push_back要高,emplace_back会少调用一次拷贝或者移动构造函数,但是前提是他俩不能混着用。

你可能感兴趣的:(c++)