左值:可以取地址的、有名字的就是左值
右值分为纯右值(C++98中提出)和将亡值(C++11提出)
- 纯右值:用于识别临时变量和一些不跟对象关联的值。比如:常量、一些运算表达式(1+3)等,不能取地址的值
- 将亡值:在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
引用:
- 右值引用:就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
- 左值引用:就是对一个左值进行引用的类型。
- 常量左值引用:是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化
左值引用的声明符号为”&”, 为了和左值区分,右值引用的声明符号为”&&”。
右值引用书写规则:类型&& 引用变量名字 = 实体 例:const int&& ra = 10;
由上图可以看出,fun()构成重载,第一个传的是左值,第二个传的是右值。
如果以下面这种方式进行函数调用,结果是怎样的呢?
结果如下:fun(b),选择调用了 void fun(int& a)函数,这就意味着,虽然b是一个右值引用,但是可以打印出b的地址,说明了b是一个左值,所以函数重载的时候选择了第一个函数。
那么搞这么复杂右值引用又有什用处呢?下面来看。。。
意义:
右值是一个临时变量,在表达式结束后就找不到了,比如上面怎么找到常量10,但如果想继续使用右值,就必须得动用拷贝构造函数了,这代价有点大,所以我们想到了右值引用这种方法,可以找到这个常量。
并且在C++11中右值引用是用来支持移动语义的。移动语义可以将资源从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
这就像我们生活中常用的剪切类似于移动语义,而复制、粘贴类似于拷贝构造,前者只是一步操作,而后者需要两步。
下面我们来看这样一份代码:是自己实现的string类
class String
{
public:
String(char* s = "")
{
if (nullptr == s)
s = "";
_s = new char[strlen(s) + 1];
strcpy(_s, s);
}
String(const String& s)
:_s(new char[strlen(s._s)+1])
{
strcpy(_s, s._s);
}
String& operator =(const String& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._s) + 1];
strcpy(tmp, s._s);
delete _s;
_s = tmp;
}
return *this;
}
~String()
{
if (_s)
delete _s;
}
private:
char* _s;
};
int main()
{
String a;
a = String("Hello");
String s(String("asd"));
}
为了解决浅拷贝的问题,自定义实现了拷贝构造和赋值。在main函数中,是一个右值,是一份临时的资源,将其赋值给s,但是,在执行时,在编译器中可以看到先调用了构造函数,再调用了赋值函数,并且还开辟了额外的空间,造成了没有必要的资源申请和释放,降低了效率,如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。
以下做出一些改变,定义转移构造函数和转移赋值函数:
这时候再执行就会调用转移赋值函数,节省了开支。
但是当我们执行的时候,却没有用到转移构造函数,这是就要用到了,move的作用就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。这样就可以调用转移构造函数了。
右值引用和转移语义的结合,对于需要动态申请大量资源的类,应该设计转移构造函数和转移赋值函数,以提高应用程序的效率。
std::forward、完美转发
C++11通过std::forward可以实现完美转发,完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数,Perfect为转发的模板函数,Func为实际目标函数,但是以下转发还不算完美,因为调用PerfectForward函数时,一个传变量a(调用左值),一次直接传10(调用右值)。但是两次都是当左值传参了,肯定将10存储为左值,产生了额外的开销
完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,就好像转发者不存在一样。传参为左值右值就调用对应的方法。
通过以下才实现了完美转发: