c++11 之 左右值引用(帮助你完全理解左右值和完美转发)

引言:

  说实话,右值引用算是一个比较划时代的新的语法,我先介绍一个场景

string getString(){
    return string("hello world");
}
string ret = getString();

  比如说一个函数如果我们想要返回一个string,这里的情况是什么呢,在没有右值之前,这里的操作是什么呢?string("hello world")是一个匿名对象他调用然后函数传值返回的过程中会产生一个临时对象,也就是说string("hello world")用于创建了一个临时对象,然后临时临时对象调用拷贝构造函数初始化ret(当然现在的编译器比较先进,可能不会产生临时对象,直接用string("hello world")初始化ret)

  不管怎样,这里一定是会调用拷贝构造,也就是说出现深拷贝的情况 ,但是这里你有没有想过,string("hello world")因为是匿名对象,马上就要销毁,这样不是非常的浪费吗,就算这里不是匿名对象,在函数栈帧销毁后,里面的string不是也会销毁吗,然后相当于我们销毁一个同时又创建一个,你不会觉得很浪费吗?当然浪费了,那么怎么解决呢

解决的方式

  我们的c++设计者同样的发现了这里的问题,那么应该怎么解决呢?这里就不再卖关子了,在c++11中提出了左右值的概念,平时我们使用的int & ,const int & 类似的引用这里统一的称作左值引用,我们新提出了一个新的概念右值,下面我给出官方给出的右值的概念


右值引用是C++11中引入的新概念,使用&&表示。它专门用于绑定到右值,即那些不具有持久存储位置的临时对象或字面量。右值引用允许开发者利用移动语义,这是一种资源转移的机制,它可以将资源从一个对象转移到另一个对象,而不需要进行复制。例如,给定int &&rref = 10;10是一个右值,rref成为其右值引用。

右值

那么我们应该怎么区分和理解左右值呢,其实本质上左右值没有本质的区别,只是我们给编译器加了一层标志方便编译器对引用的类型进行进一步的划分。右值很多时候我更愿意称他为将亡值,什么意思,有很多种场景

void push_back(const T& value); // 左值引用
void push_back(T&& value); // 右值引用

想想一下我们在push_back的时候,如果传入的是一个匿名对象,比如

class Data{
public:
    Data(int data):_data(data);
    {}
private:
    int _data;
};
std::vector<Data> vd;
vd.push_back(Data(1));

如果没有右值之前我们应该怎么理解这段的代码呢?我们通过构造函数创建了一个匿名的对象,然后传递给push_back的底层,底层通过new Data(value)的方式调用拷贝构造创建一个对象并且连接到vector的尾部,然后函数退出后,匿名对象调用析构函数,你不觉得这里有个问题吗。。。

为什么我们不能直接把这个对象直接给我们的vector,非得让vector照着我们的对象创建一个,因为我们这里的Data匿名对象已经是一个将亡的值了,这样做才显得更加的合理。

是不是有一点感觉了。。。 下面我继续解释     

class Array
{
public:
    Array(size_t size):_begin(new int[size]),_size(size)
    {}
    Array(const Array& arr){  // 拷贝构造

        _begin = new int[arr._size];
        for(size_t i = 0;i < _size;i++) 
            _begin[i] = arr._begin[i];
    }

    Array(Array&& arr)  // 移动构造
    {
        std::swap(_begin,arr._begin);
        std::swap(_size,arr._size);
    }
    ~Array() {
        delete [] _begin;
        _size = 0;
    }
private:
    int* _begin;
    size_t _size;
}

比如这段代码Array tmp(Array(5));

  编译器会怎么理解,我们的Array(5)是一个匿名对象,编译器会认为他是一个将亡值,所以这里调用的是移动构造,这样的效率是不是提高了很多,原来我们需要调用构造 + 拷贝构造  ,有了移动构造函数,就变成了拷贝构造 + 交换我们彼此的值。本来你就要析构了,还不如直接把你的给我呢。我愿意称之为程序上面上的“拿来主义”。

移动构造

  刚在的文章中,我们已经提到了移动构造的相关的内容了,那么,原来我们编译器只能有了一个对象创建一个相同类型的对象只有拷贝构造,现在我们有了移动构造,简单来说就是如果你已经将亡了,不如直接就将你的数据给我吧,这里性能的提升主要提升在深拷贝的情况下,我们可以减少很多没有必要的开空间的行为。

移动赋值函数

  有了拷贝构造还有赋值重载函数,当然有了移动构造还有移动赋值函数,这里很好理解对吧。

左右值之间的细节和规则

左右值之间互相间的引用的关系

  1. 左值可以引用右值吗,当然可以,但要看你怎么用。 首先,语言在设计的时候不能破坏以前的原理。如果新的语法破坏了原来的语法,这显然是不合理的。

  匿名对象我们可以将他处理成右值,我们以前在处理的时候怎么引用匿名对象呢。当然就是const T & ,所以我们的const T & 同样可以引用右值,这也揭示我们右值是不能被改变的(至少说在他成为右值之后是不能被改变的)。   

  2. 右值可以引用左值吗? 当然不行啦,右值是将亡值,随便给你个变量,你能说他是将亡值吗?

总结 : const修饰的引用可以引用右值,右值不能引用左值

std::move

  那么我们可不可以将左值变成右值呢,相当于怎么说呢,我们能不能手动将一个值设置成为将亡值呀? 

  答案当然是可以,std::move()就是一个c++标准提供的函数,他的作用就是将一个左值引用变成一个右值引用相当于(const) T& -> T&&。

完美转发

引用折叠

  其实到这里位置,我们已经解决了本篇文章开头提到的问题,就是如果我们返回一个string的类型,那么我们其实可以非常自然的想到return std::move(buffer);将buffer作为右值返回,但是我们的编译器会做一些优化,所以我们即使直接return buffer,编译器也是把他当做右值来看待

那么什么是完美转发呢?

c++委员会想到了另一个问题,我们在使用模版的时候。

template <class T>
void push_back(const T& value);

template <class T>
void push_back(T&& value);

如果我们每次都这样写,会不会显得太麻烦,就是说我们对于每个涉及到拷贝的函数,我们都要考虑设计两个重载的函数。但是这样会不会太麻烦了。所以c++标准又设计出来的引用折叠的概念。

template <class T>
void push_back(T&& value);

我们只需要直线这样的一个函数就行,在T没有实例化之前,我们可以把他成左右值游离态。他既可以是左值又可以是右值。你传递一个右值的参数,他就是右值引用,你传递一个左值的参数,他就是左值引用

这里有一个注意点:

1. 只能是模版才能是引用折叠,引用模版一旦确定,他将固定,相当于模版一旦固定,他就是确定的。

完美转发

这样又有了一个问题,如果我们引用折叠的函数还调用了其他的函数,怎么理解呢?


void push_back(int& value)
{
    // nothing
}

void push_back(int&& value)
{
    // nothing
}

template <class T>
void Function(T&& value)
{
    push_back(value);
    push_back(std::move(value));
}

虽然说value可能是是右值引用,c++万物皆对象,value表示的是一个对象,他右值引用了一个变量。但是value本身是一个左值。 

所以我们为了保证类型的统一,这里使用的是std::move,但是如果我们不知道应该左值还是右值,这里就提出了完美转发,你是左值我就给你变成左值,你是右值我就给你变成右值,总之就是保证类型的不变性。

push_back(std::forward<T>(value));// 保证类型传递的稳定

总结

所以总结一下左右到底有什么用呢,无非就是c++在不断的发展的过程中发现了一些地方进行着没有必要的拷贝构造而进行的优化,如果你比较熟悉模版,这应该对你的提升代码的效率会有比较大的提升。

这也可以解释你的一个疑惑(不知你们有没有)???

最开始在刷leetcode的时候我很好奇为什么要直接返回一个vector,list之类的容器,而不是将vector<>&作为一个函数的参数,因为返回不会因为拷贝浪费很多的时间吗,学了这里,你应该就懂了,我们的vector拥有自己的移动构造函数,本质上就是将内部的指针进行了交换。

总而言之,左右值的出现让很多深拷贝的地方变成了浅拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值