精华内容
下载资源
问答
  • 主要介绍了C++ lambda 捕获模式与右值引用的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 1.首先区分左值和右值  左值是表达式结束后依然存在的持久对象  右值是表达式结束时不再存在的临时对象  便捷方法:对表达式取地址,如果能,则为左值,否则为右值  举例:  int a = 10  int b = 20  ...
  • 第一次接触c++move操作就懵逼了,一直想探个究竟,但是右值以及右值引用思考了好长时间,就是不得要领,今天终于有所收获,写下第一篇博客,一方面为了帮助一些刚入门的朋友,另一方面也是帮助自己今后复习。...
  • 对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂。最近翻翻资料感觉突然开窍,因此顺便记录下C++11中的右值引用、转移语义和完美转发,方便大家查阅参考。
  • 右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念。右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&。 如果...
  • 为了支持移动操作,新标准引入了一种新的引用类型——右值引用,就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用右值引用一个重要的特性就是只能绑定到将要销毁的对象。 左值和右值是表达式的属性,...
  • 右值引用

    2020-10-19 15:35:13
    右值引用 1. 什么是右值引用 右值引用是C++11新加的一种引用类型,是一种仅能绑定到右值上的引用。不同于左值引用仅用一个&表示,右值引用用两个&&表示。 int x{ 5 }; int& lref{ x }; // l-value ...

    右值引用

    1. 什么是右值引用

    右值引用是C++11新加的一种引用类型,是一种仅能绑定到右值上的引用。不同于左值引用仅用一个&表示,右值引用用两个&&表示。

    int x{ 5 };
    int& lref{ x }; // l-value refrence initialized with l-value x 
    int&& rref{ 5 } // r-value refrence initialized with r-value 5
    

    右值引用有两个非常有用的性质: 1. 右值引用将初始化他们的对象的寿命延长到右值引用的寿命;2. 非常量的右值引用允许修改右值。

    看一个实际例子:

    #include <iostream>
     
    class Fraction
    {
    private:
    	int m_numerator;
    	int m_denominator;
     
    public:
    	Fraction(int numerator = 0, int denominator = 1) :
    		m_numerator{ numerator }, m_denominator{ denominator }
    	{
    	}
     
    	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
    	{
    		out << f1.m_numerator << '/' << f1.m_denominator;
    		return out;
    	}
    };
     
    int main()
    {
    	auto &&rref{ Fraction{ 3, 5 } }; // r-value reference to temporary Fraction
    	
        // f1 of operator<< binds to the temporary, no copies are created.
        std::cout << rref << '\n';
     
    	return 0;
    } // rref (and the temporary Fraction) goes out of scope here
    

    这段代码打印出:

    3/5
    

    Fraction{ 3, 5 }是一个匿名对象(临时对象),在这行语句结束的时候就出了作用域,本来应该被销毁掉,但是我们用了一个右值引用来绑定它,因此延长了它的生命期,直到main函数结束,局部变量rref被销毁的时候,这个临时对象才会被销毁。
    再看另外一个例子:

    #include<iostream>
    int main()
    {
        int &&ref{ 5 };
        rref = 10;
        std::cout << rref << std::endl;
        return 0;
    }
    

    这段代码执行结果是:

    10
    

    这里用字面值初始化一个右值引用,会创建一个临时对象,我们可以通过右值引用来修改这个对象。

    2. 右值引用作为函数参数

    右值引用最有用的地方在于作为函数参数,尤其是在写重载函数时,希望对传入的左值和右值表现出不同的行为。

    void fun(const int& lref)
    {
        std::cout << "l-value reference to const." << std::endl;
    }
    void fun(int &&rref)
    {
        std::cout << "r-value reference." << std::endl;   
    }
    
    int main()
    {
        int x{ 5 };
        fun(x); // l-value argument calls l-value version of fun()
        fun(5); // r-value argument calls r-value version of fun()
        return 0;
    }
    

    这段代码打印出:

    l-value reference to const.
    r-value reference.
    

    可以看出,当传入的参数是左值时,调用的是左值版本的fun(), 当传入的参数是右值时,调用的是右值版本的fun()。可是这有什么用呢?这对于移动语义来说是一个非常重要的特性,后面会继续讨论。
    再看一个有意思的例子:

    int &&ref{ 5 };
    fun(ref);
    

    ref是一个右值引用,那么fun(ref)调用的是右值引用版本吗?事实上,这里调用的是左值版本的fun()函数。虽然ref是一个右值引用,但是这仅说明它绑定的对象是一个右值,它本身是一个局部变量,是一个左值,因此这行代码调用的是左值版本的fun()。

    3. 不要返回右值引用

    在绝大多数情况下,你都不应该返回右值引用。因为右值引用绑定的对象在出作用域之后就会被销毁,因此从函数返回,你智能得到一个"hanging reference"。

    参考资料

    [1].https://www.learncpp.com/cpp-tutorial/15-2-rvalue-references/

    展开全文
  • 下面小编就为大家带来一篇浅谈C++左值引用和右值引用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 主要介绍了C++11 模板参数的“右值引用”是转发引用吗,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 1、右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题。但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了CopyElision、RVO(包括NRVO)等编译器优化技术...
  • 上一篇博客剖析了引用的底层实现原理,文中所述的“引用”默认表示...同理,右值也可以分为两类:非常量右值和常量左值。 左值引用举例说明: int a=10; //非常量左值(有确定存储地址,也有变量名) const int a1=10;

    上一篇博客剖析了引用的底层实现原理,文中所述的“引用”默认表示为左值引用。

    一、左值与左值引用

    什么是左值引用呢?
    左值引用,就是绑定到左值的引用,通过&来获得左值引用
    那么,什么是左值呢?
    左值,就是在内存有确定存储地址、有变量名,表达式结束依然存在的值。

    左值可以分为两类:非常量左值和常量左值;同理,右值也可以分为两类:非常量右值和常量左值。
    左值引用举例说明:

    int a=10;              //非常量左值(有确定存储地址,也有变量名)
    const int a1=10;       //常量左值(有确定存储地址,也有变量名)
    const int a2=20;       //常量左值(有确定存储地址,也有变量名)
     
    //非常量左值引用
    int &b1=a;            //正确,a是一个非常量左值,可以被非常量左值引用绑定
    int &b2=a1;           //错误,a1是一个常量左值,不可以被非常量左值引用绑定
    int &b3=10;           //错误,10是一个非常量右值,不可以被非常量左值引用绑定
    int &b4=a1+a2;        //错误,(a1+a2)是一个常量右值,不可以被非常量左值引用绑定
    
    //常量左值引用
    const int &c1=a;      //正确,a是一个非常量左值,可以被非常量右值引用绑定
    const int &c2=a1;     //正确,a1是一个常量左值,可以被非常量右值引用绑定
    const int &c3=a+a1;   //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
    const int &c4=a1+a2;  //正确,(a1+a2)是一个常量右值,可以被非常量右值引用绑定
    

    可以归纳为:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。

    二、右值与右值引用

    顾名思义,什么是右值引用呢?
    右值引用,就是绑定到右值的引用,通过&&来获得右值引用
    那么,什么又是右值呢?
    右值,就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值。

    右值引用举例说明:

    int a=10;             //非常量左值(有确定存储地址,也有变量名)
    const int a1=20;      //常量左值(有确定存储地址,也有变量名)
    const int a2=20;      //常量左值(有确定存储地址,也有变量名)
    
    //非常量右值引用
    int &&b1=a;            //错误,a是一个非常量左值,不可以被非常量右值引用绑定
    int &&b2=a1;           //错误,a1是一个常量左值,不可以被非常量右值引用绑定
    int &&b3=10;           //正确,10是一个非常量右值,可以被非常量右值引用绑定
    int &&b4=a1+a2;        //错误,(a1+a2)是一个常量右值,不可以被非常量右值引用绑定
    
    //常量右值引用
    const int &&c1=a;      //错误,a是一个非常量左值,不可以被常量右值引用绑定
    const int &&c2=a1;     //错误,a1是一个常量左值,不可以被常量右值引用绑定
    const int &&c3=a+a1;   //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
    const int &&c4=a1+a2;  //正确,(a1+a2)是一个常量右值,不可以被常量右值引用绑定
    

    可以将右值引用归纳为:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。

    从上述可以发现,常量左值引用可以绑定到右值上,但右值引用不能绑定任何类型的左值,若想利用右值引用绑定左值该怎么办呢?
    C++11中提供了一个标准库move函数获得绑定到左值上的右值引用,即直接调用std::move告诉编译器将左值像对待同类型右值一样处理,但是被调用后的左值将不能再被使用

    std::move使用举例说明:

    int a=10;                 //非常量左值(有确定存储地址,也有变量名)
    const int a1=20;          //常量左值(有确定存储地址,也有变量名)
    
    //非常量右值引用
    int &&d1=std::move(a);    //正确,将非常量左值a转换为非常量右值,可以被非常量右值引用绑定
    int &&d2=std::move(a1);    //错误,将常量左值a1转换为常量右值,不可以被非常量右值引用绑定
    
    //常量右值引用
    const int &&c1=std::move(a);      //正确,将非常量左值a转换为非常量右值,可以被常量右值引用绑定
    const int &&c2=std::move(a1);     //正确,将常量左值a1转换为常量右值,可以被常量右值引用绑定
    

    可以发现,编译器利用std::move将左值强制转换为相同类型的右值之后,引用情况跟右值是一模一样的。

    三、右值引用与左值引用的区别

    (1)左值引用绑定到有确定存储空间以及变量名的对象上,表达式结束后对象依然存在;右值引用绑定到要求转换的表达式、字面常量、返回右值的表达式等临时对象上,赋值表达式结束后就对象就会被销毁。
    (2)左值引用后可以利用别名修改左值对象;右值引用绑定的值不能修改。

    四、引入右值引用的原因

    (1)替代需要销毁对象的拷贝,提高效率:某些情况下,需要拷贝一个对象然后将其销毁,如:临时类对象的拷贝就要先将旧内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,就可以让新对象直接使用旧内存并且销毁原对象,这样就减少了内存和运算资源的使用,从而提高了运行效率;
    (2)移动含有不能共享资源的类对象:像IO、unique_ptr这样的类包含不能被共享的资源(如:IO缓冲、指针),因此,这些类对象不能拷贝但可以移动。这种情况,需要先调用std::move将左值强制转换为右值,再进行右值引用。

    右值引用的使用方法可参考博客:https://blog.csdn.net/china_jeffery/article/details/78520237

    展开全文
  • 右值引用详解

    2020-12-24 22:50:13
    ##右值引用的作用 ###为什么要有右值引用   我们知道,当类成员中存在指针成员时,使用复制拷贝构造,需要进行深拷贝,但问题在于:我们真的任何时候都需要深拷贝吗?首先来看一下下面的几个代码片段。 片段1: ...


    何谓右值

      一个最简单判断左值、右值的方式是:等号左边的值即左值,等号右边的值即右值;但为了更加严谨,我们这样定义:能够取地址的,有名字的就是左值;反之,不能取地址,没有名字的就是右值。

    右值引用

      右值引用简单理解,就是绑定到左值的引用,右值引用的特点是:它将让其所绑定的右值重获新生,即使该右值本身是一个临时变量,但是它本身却不能绑定任何左值。比如:

    int c;
    int && d = c; //错误,右值不能绑定左值
    
    T && a = ReturnRvalue(); //#1
    T b = ReturnRvalue(); //#2
    

      在#1中,我们声明了一个名为a的右值引用,其值等于ReturnRvalue()返回的临时变量的值。而#2,b则是通过函数返回的临时量再进行构造生成的值。

      需要注意的是,上述代码的前提是:ReturnRvalue()函数本身返回的就是一个右值,所以ReturnRvalue必须类似如下形式:

    T && ReturnRvalue()
    {
    	return T();
    }
    

      对于返回右值引用的函数来说,支持右值声明的绑定,不支持非常量左值,却支持非常量左值。具体可以参考下图:
    在这里插入图片描述
      从上图中,我们也可以知道,常量右值引用没有用途。
      代码片段如下:

    T &e = ReturnRvalue();  //#1
    const T & f = ReturnRvalue(); //#2
    

      如上所示,#1将会编译报错,#2却能编译通过,这个主要是由于c++98标准的定义问题,这里不再讨论该细节,我们只需要知道即可。

      右值引用除了作为返回值,作为参数也是可以的,如下:

    void AcceptRvalueRef(Copyable && s)
    {
    	...
    }
    

      和左值引用相同,我们也不需要进行额外拷贝。

    右值引用与其他对比

    在这里插入图片描述
      上面的思维导图主要对比了右值引用在作为参数及返回值时与其他类型的不同,可以得出以下几点:

    • 右值引用无论作为参数还是返回值,都可以使用临时变量,并且由于其可以窃取临时变量中的内存,导致其效率较高;
    • 常量左值引用是万能类型,当参数是常量左值时,我们传入右值也可以;当返回值是右值时,使用常量左值也可以接收。
    • 左值引用无论是作为参数还是返回值,都要求其不能使用临时变量。

      当右值引用作为构造函数参数时,这就是所谓的移动构造函数,也就是所谓的移动语义。

    右值引用与移动语义

      我们知道,当类成员中存在指针成员时,使用复制拷贝构造,需要进行深拷贝,但问题在于:我们真的任何时候都需要深拷贝吗?首先来看一下下面的几个代码片段。

    片段1

    String s;
    String p = s;
    ...
    

      上面无疑是需要深拷贝的,因为无论s,还是p,都可能在我们后面的代码里面继续用到。

    片段2

    String GetTemp() {return String();}
    int main()
    {
    	String str = GetTemp();
    }
    

      这里代码中实际只用到了str,但是实际上却调用了一次构造(GetTemp函数中调用String构造生成临时对象)、两次拷贝构造(一次是GetTemp函数调用拷贝构造生成临时对象用于返回、一次是str接收)、三次析构。这里拷贝构造调用了两次深拷贝,但是最后实际使用到的对象却只有str,因此,可以看出,这里有一次深拷贝是多余的。

      当堆内存很大时,多余的深拷贝以及其对象的堆内存析构耗时就会变的很可观,那么是否有一种方式,可以让函数中的返回的临时对象空间是否可以不析构,而可以重用呢?

      基于上述原因,因此c++11提供了移动构造来解决上述问题。移动构造也是基于右值引用来实现的。

      针对上面片段2存在的问题,可以使用移动构造进行解决,下面是移动构造的示例:

    class HasPtrMem{
    public:
    	HasPtrMem():d(new int(3)){
    	...
    	}
    
    	HasPtrMem(const HasPtrMem& h) : d(new int(*h.d)){
    
    	}
    
    	HasPtrMen(HasPtrMem && h) : d(h.d){    //#1
    	h.d = nullptr;
    	}
    	
    	...
    	int *d;
    }
    
    HasPtrMen GetTemp() {
    	HatPtrMem h;
    	return h;
    }
    
    int main()
    {
    	HasPtrMem a = GetTemp();
    	...
    }
    

      #1所示的即移动构造函数,它与拷贝构造函数不同的是,它接收的是一个右值引用的参数,即HasPtrMem && h,移动构造函数使用参数h的成员d初始化了本对象的成员d初始化了本对象的成员d(而不是像构造函数一样需要分配内存,然后再将内容一次拷贝到新分配的内存中),而h的成员d随后就被置空。

       这里的“偷”堆内存,就是指将对象d指向h.d所指的内存这一条,除此之外,我们还要讲h的d置为空指针,这是因为再移动构造以后,临时对象会被析构,如果不改变h.d的指向的话,那么我们“偷”来的堆内存也被析构掉了

       那么移动构造函数什么时候才会被触发呢?事实上,我们也提供了拷贝构造函数,从外部调用形式来看,拷贝构造及移动构造调用没有分别,那么怎么确保我们调用的是移动构造呢?这就涉及到临时对象的问题。

    右值引用与std::move

      std::move主要用于将左值强行转换为右值,需要注意的是,被转化的左值生命周期并没有因这种转换而改变。但是在使用std::move时,我们却需要注意:一旦该左值被转换为右值,如果和移动语义结合使用,那么该左值的生命周期就将结束,如果此后还继续使用改左值,那么就会出现严重错误。如下所示:

    #include <iostream>
    
    using namespace std;
    classHugeMen{
    public:
    	HugeMem(int size) : sz(size > 0 size : 1){
    	c = new int(sz);
    	}
    
    	HugeMem(HugeMem && hm) : sz(hm.sz) , c(hm.c){
    	hm.c = nullptr;
    	}
    	
    	int sz;
    	int *c;
    }
    
    int main()
    {
    	HugeMem a;
    	HugeMem c(move(a));
    	cout << *a.c << endl;
    }
    

      如上式中,a由于移动语义,其堆内存实际已被释放,后面继续调用,那么就会报错。
      基于此,所以我们应该注意:应当确保使用std::move用于移动语义的变量是一个临时量。下面是把std::move用于移动语义的正确姿势:

    Class Moveable{
    public:
    	...
    	Moveable(Moveable &&m) :
    		i(m.i) ,h(move(m.h)){ //#1
    		m.i = nullptr;
    	}
    	
    int *i;
    HugeMem h;
    }
    
    Moveable a(GetTemp());
    

      分析上述代码可以发现,GetTemp()临时对象将很快析构,可以避免出现错误。
      这里考虑一下,如果#1所在的地方HugeMem不支持移动语义怎么办,这也没多大问题,因为此时会调用其常量左值拷贝函数(上文中已经说明了常量左值是接收右值的),因此也不会有多大问题。基于此,因此我们在编写移动构造函数时应总是将拥有堆内存、文件句柄的资源从左值转换为右值。

    移动语义与std::move

       移动语义与std::move结合时,要格外注意不要误用,下面是一个错误使用的示例:

    int main()
    {
        Moveable a;
        Moveable c(move(a)); 
        cout << *a.i << endl;
        return 0;
    }
    

       a本身是一个左值,但是被move强转为右值,但是a的生命周期又还没有结束,根据上述移动语义的说明,我们可知:a指向i的内存已经被c窃取了,a.i指针指向空,那么一旦输出i的值,那么程序就会出现错误。
       从上面示例我们可以得到一个注意事项,即:我们在使用move语义时,一定要确保被强转的左值很快会被析构,否则就会带来隐患。

       下面提供一种移动语义与std::move的正确用法:

    class HugeMem{
    public:
        HugeMem(int size) : sz(size > 0 ? size : 1){
            c = new int[sz];
        }
    
        ~HugeMem() {delete[] c;}
    
        HugeMem(HugeMem && hm) : sz(hm.sz),c(hm.c){
            hm.c = nullptr;
        }
    
        int *c;
        int sz;
    };
    
    class Moveable{
    public:
        Moveable() : i(new int(3)),h(1024){}
        ~Moveable() {delete i;}
        Moveable(Moveable && m) :
        i(m.i),h(move(m.h)){
         m.i = nullptr;   
        }
        
        int *i;
        HugeMem h;
    };
    
    Moveable GetTemp(){return Moveable();}
    
    int main()
    {
        Moveable a(GetTemp());
        return 0;
    }
    

      Moveable对象中包含了HugeMem对象,HugeMem类中存在堆内存的分配的成员,如果Moveable传入了一个临时HugeMem对象,那么毫无疑问HugeMem启用移动语义,同时我们同时可以使用std::move将HugeMem的成员转换为右值,从而对该成员也启用移动语义(前提是该成员要存在移动构造函数)。

    移动语义注意事项

    • 移动构造函数中要避免使用const右值引用,因为我们最终是要修改右值引用中堆内存指向的。
    • C++11中,实际拷贝/移动构造函数有以下三个版本:
      T Object(T &)
      T Object(const T &)
      T Object(T &&)
        一般来说,编译器会隐式的生成一个移动构造函数,不过如果我们自己声明了自定义的拷贝构造函数、拷贝赋值函数、移动赋值函数、析构函数中的一个或多个,那么编译器都不会再生成默认版本。默认版本的移动构造一般也是按位拷贝,这对实现移动语义来说是不够的,通常情况下,如果要实现移动语义,都需要我们自定义移动构造函数。当然,如果类中不包含堆内存,实不实现移动语义都不重要。
        考虑到常量的左值引用是万能的,假设我们传入参数类型为右值,但是又没有实现移动语义会怎么样呢?那么就会进入常量拷贝构造函数,这就确保了即使移动构造不成,还可以拷贝。

    移动语义与swap

      移动语义可以实现高效的swap函数,如下:

    template<class T>
    void swap(T& a,T& b)
    {
    	T tmp(move(a));
    	a = move(b);
    	b = move(tmp);
    }
    

      上述代码完全避免了资源的释放与申请,从而完成高效置换。

    完美转发

      所谓完美转发,就是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另一个函数。由于拷贝问题的存在,所以完美转发一般不包括值传递。如下:

    template<typename T>
    void IamForwording(T t){ IrunCodeActually(t); }
    

      针对上面的例子来说,就是希望:IamForwording传入左值对象,那么IrunCodeActually就获得左值对象;IamForwording传入右值对象,那么IrunCodeActually就获得右值对象。换一句话说,假定IrunCodeActuall有重载左值有右值版本,那么IamForwording传左值就进入IrunCodeActuall左值版本,传右值就进入IrunCodeActuall右值版本。我们当然可以实现两个版本的IamForwording转发函数以对应IrunCodeActuall,但是这样过于繁杂,所以我们更期望实现IamForwording转发函数的模板函数。
      虽然貌似很简单,但实际并没有我们想象的那么简单,比如当转发函数为右值,而模板参数为左值,那么我们面临的第一个问题就是,如何确定转发函数提供的实际类型。代码如下:

    template<typename T>
    void IamForwording(T &&t){ IrunCodeActually(t); }
    
    T a;
    IamForwording(a); //a是左值,而转发函数参数又是右值,此时目标函数IrunCodeActually中的是左值还是右值?
    

      基于上述原因,所以c++11提供了引用折叠,引用折叠一方面确定了左值右值类型叠加时的类型确定规则,另一方面该规则确保了转发者与接收者的类型一致。

      我们可以用两条语句来抽象表示转发者与接收者的参数类型叠加问题:

    typedef T& TR;
    TR& v;
    

      如上述代码所示,T、TR的类型可能是左值,也可能右值,那么v最后是左值还是右值呢?针对此,C++11定义了以下的引用折叠规则:
      在这里插入图片描述
      我们可以把TR认为是转发函数参数类型,v为接收函数类型,v的实际类型为叠加后的类型。从上表可以看出一旦定义中出现了左值引用,那么引用这得优先将其折叠为左值引用。

      从上表也可以看出,除了第5种(TR 为 T&&,v为TR&,而v实际为A&)类型外,其他都是不需要进行额外转发就能够确保模板参数类型TR和目标函数参数类型v一致。可见引用折叠规则独自无法完成完美转发。因此,C++11在此基础上又提出了std::forward

      分析上表第5种情况可以看出,实际类型为A&,但是我们传入的是T&&,要确保目标函数也收到T&&,那么就只能A&转换为T&&,很明显,这是左右值的转换,我们很自然想起了std::move,但是c++11为了在功能上区别完美转发,所以使用std::forward取代std::move。

      完美转发主要用于函数模板中,下面是完美转发示例:

    #include <iostream>
    #include <list>
    
    using namespace std;
    
    void RunCode(int && m) {}
    void RunCode(int &m) {}
    void RunCode(const int && m) {}
    void RunCode(const int & m) {}
    
    template<typename T>
    void PerfectForward(T &&t){RunCode(forward<T>(t));}
    
    int main()
    {
        int a;
        int b;
        const int c = 1;
        const int d = 0;
    
        PerfectForward(a);  // lvalue ref
        PerfectForward(move(b));  // rvalue ref
        PerfectForward(c);  // const lvalue ref
        PerfectForward(move(d)); // const rvalue ref
    }
    

      从上面代码种可以看到,当模板类型为左值时,其进入了目标函数的左值版本,当模板类型为右值时,其进入了目标函数的右值版本,转发函数可以视作不存在,这就是完美转发。

    相关代码链接:https://github.com/KevinCoders/MyStudy/blob/master/base/right_value.cpp

    展开全文
  • cpp代码-左值 和 右值 ---- 左值引用(就是别名) 和 右值引用
  • 主要介绍了c++ 移动语义与右值引用的相关资料,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下
  • C++右值引用

    2020-10-28 17:42:39
    文章目录前言一、右值引用是什么?二、右值区分与应用?1.应用:右值引用的移动构造和移动赋值,可以减少拷贝2.应用:当传值返回值,返回是右值,结合前面学的移动构造和移动赋值.3.应用:右值引用去做函数的参数,...


    前言

    C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通
    过指针来实现的,因此使用引用,可以提高程序的可读性。

    一、右值引用是什么?

    为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
    C++98就提出了引用的概念,引用就给一个对象取别名。
    C++98 左值引用
    C++11 右值引用
    不管是左值引用,还是右值引用,他们都是给对象取别名,
    不过左值引用主要是给左值取别名,右值引用主要是给右值取别名。

    什么是左值?什么是右值?
    // =左边就是左值?右边的就是右值? 注意这个是一个C语法就留下的坑,就像左移和右移一样,
    这里左右不是方向。左边的值也不一定左值,右边的值不一定是右值
    int x1 = 10; int x2 = x1; int x3 = x1+x2; 这里x1是左值,10是右值,x2是左值,x1+x2表达式返回值就是右值
    可以修改就可以认为是左值,左值通常是变量
    右值通常是常量,表达式或者函数返回值(临时对象)

    int main()
    {
    	int x = 1, y = 2;
    	
    	// 左值引用的定义
    	int a = 0;
    	int& b = a;
    
    	// 左值引用不能引用右值, const左值引用可以
    	//int& e = 10; 
    	//int& f = x + y;
    	const int& e = 10;
    	const int& f = x + y;
    
    
    	// 右值引用的定义
    	int&& c = 10;
    	int&& d = x + y;
    
    	// 右值引用不能引用左值,但是可以引用move后左值
    	//int&& m = a;
    	int&& m = move(a);
    
    	return 0;
    }
    
    template<class T>
    void f(const T& a)
    {
    	cout << "void f(const T& a)" << endl;
    }
    
    template<class T>
    void f(const T&& a)
    {
    	cout << "void f(const T&& a)" << endl;
    }
    
    int main()
    {
    	int x = 10;
    	f(x); // 这里会匹配左值引用参数的f
    	f(10);// 这里会匹配右值引用参数的f
    
    	return 0;
    }
    

    二、右值区分与应用?

    C++11又将右值区分为:纯右值和将亡值
    纯右值:基本类型的常量或者临时对象
    将亡值:自定义类型的临时对象

    结论:所有深拷贝类(vector/list/map/set…),都可以加两个右值引用做参数的移动拷贝和移动赋值

    1.应用:右值引用的移动构造和移动赋值,可以减少拷贝

    class String
    {
    public:
    	String(const char* str = "")
    	{
    		_str = new char[strlen(str) + 1];
    		strcpy(_str, str);
    	}
    
    	// s2(s1)
    	String(const String& s)
    	{
    		cout << "String(const String& s)-深拷贝-效率低" << endl;
    
    		_str = new char[strlen(s._str) + 1];
    		strcpy(_str, s._str);
    	}
    
    	// s3(右值-将亡值)
    	String(String&& s)
    		:_str(nullptr)
    	{
    		// 传过来的是一个将亡值,反正你都要亡了,我的目的是跟你有一样大的空间,一样的值
    		// 不如把你的控制和只给我
    		cout << "String(String&& s)-移动拷贝-效率高" << endl;
    		swap(_str, s._str);
    	}
    
    	String& operator=(const String& s)
    	{
    		return *this;
    	}
    
    	String& operator=(String&& s)
    	{
    		return *this;
    	}
    
    
    	~String()
    	{
    		delete[] _str;
    	}
    private:
    	char* _str;
    };
    
    String f(const char* str)
    {
    	String tmp(str);
    	return tmp; // 这里返回实际是拷贝tmp的临时对象
    }
    
    int main()
    {
    	String s1("左值");
    	String s2(s1);                      // 参数是左值
    	String s3(f("右值-将亡值"));        // 参数是右值-将亡值(传递给你用,用完我就析构了)
    
    	return 0;
    }
    

    2.应用:当传值返回值,返回是右值,结合前面学的移动构造和移动赋值.

    3.应用:右值引用去做函数的参数,减少拷贝

    //std::vector::push_back
    //void push_back(const value_type& val);
    //void push_back(value_type&& val);
    //
    //std::list::push_back
    //void push_back(const value_type& val);
    //void push_back(value_type&& val);
    //
    //std::set::insert
    //pair<iterator, bool> insert(const value_type& val);
    //pair<iterator, bool> insert(value_type&& val);
    
    int main()
    {
    	vector<string> v;
    	string s1("左值");
    	string s2("左值");
    
    	int val = 1234;
    
    	// push_back中调用的是string的拷贝构造
    	v.push_back(move(s2));             // void push_back(const value_type& val);
    
    	// push_back中调用的是string的移动构造
    	v.push_back("右值");         //void push_back(value_type&& val);
    	v.push_back(to_string(val)); // void push_back(value_type&& val);
    
    	v.emplace_back(s1); // v.emplace_back(move(s1)); 
    	v.emplace_back("右值");
    
    	vector<pair<string, string>> vp;
    	vp.push_back(make_pair("右值", "右值"));
    
    	pair<string, string> kv("左值", "左值");
    	vp.push_back(kv);
    
    	vp.emplace_back(make_pair("右值", "右值"));
    	vp.emplace_back(kv);
    	vp.emplace_back("右值", "右值"); // 体现emplace_back模板可变参数特点的地方
    
    	return 0;
    }
    

    三、完美转发

    右值引用会第二次之后的参数传递过程中右值属性丢失,下一层调用会全部识别为左值
    完美转发解决

    1.没使用完美转发

    void Fun(int &x){ cout << "lvalue ref" << endl; }
    void Fun(const int &x){ cout << "const lvalue ref" << endl; }
    void Fun(int &&x){ cout << "rvalue ref" << endl; }
    void Fun(const int&& x){ cout << "const rvalue ref" << endl; }
    
    template<typename T>
    void PerfectForward(T &&t)
    { 
    	Fun(t); 
    }
    
    int main()
    {
    	PerfectForward(10); // rvalue ref
    
    	int a;
    	PerfectForward(a); // lvalue ref
    	PerfectForward(std::move(a)); // rvalue ref
    
    	const int b = 8;
    	PerfectForward(b); // const lvalue ref
    	PerfectForward(std::move(b)); // const rvalue ref
    
    	return 0;
    }
    

    在这里插入图片描述
    可以看出全部都是左值

    2.使用forward后

    void Fun(int &x){ cout << "lvalue ref" << endl; }
    void Fun(const int &x){ cout << "const lvalue ref" << endl; }
    void Fun(int &&x){ cout << "rvalue ref" << endl; }
    void Fun(const int&& x){ cout << "const rvalue ref" << endl; }
    
    template<typename T>
    void PerfectForward(T &&t)
    { 
    	Fun(std::forward<T>(t));
    }
    
    int main()
    {
    	PerfectForward(10); // rvalue ref
    
    	int a;
    	PerfectForward(a); // lvalue ref
    	PerfectForward(std::move(a)); // rvalue ref
    
    	const int b = 8;
    	PerfectForward(b); // const lvalue ref
    	PerfectForward(std::move(b)); // const rvalue ref
    
    	return 0;
    }
    

    在这里插入图片描述

    总结

    右值引用做参数和作返回值减少拷贝的本质是利用了移动构造和移动赋值
    左值引用和右值引用本质的作用都是减少拷贝,右值引用本质可以认为是弥补左值引用不足的地方, 他们两相辅相成

    左值引用:解决的是传参过程中和返回值过程中的拷贝
    做参数:void push(T x) -> void push(const T& x) 解决的是传参过程中减少拷贝
    做返回值:T f2() -> T& f2() 解决的返回值过程中的拷贝
    ps:但是要注意这里有限制,如果返回对象出了作用域不在了就不能用传引用, 这个左值引用无法解决,等待C++11右值引用解决

    右值引用:解决的是传参后,push/insert函数内部将对象移动到容器空间上的问题.+传值返回接收返回值的拷贝
    做参数: void push(T&& x) 解决的push内部不再使用拷贝构造x到容器空间上,而是移动构造过去
    做返回值:T f2(); 解决的外面调用接收f2()返回对象的拷贝,T ret = f2(),这里就是右值引用的移动构造,减少了拷贝

    展开全文
  • 该篇介绍了左值和右值的区别、左值引用的概念、右值引用的概念、std::move()的本质、移动构造函数、移动复制运算符和RVO。 1. 左值和右值 首先来介绍一下左值和右值的区别,内容参考于《C++ primer 5th》4.1。 当一...
  • 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一。这篇文章主要介绍了C++11右值引用和std::move语句实例解析,非常不错,具有参考借鉴价值,需要的朋友可以参考下
  • 详解C++中右值引用

    千次阅读 多人点赞 2020-01-14 11:35:52
    98中的引用 概念 特性 引用的使用场景 三种传参方式效率的比较 探索:引用的底层实现方式----->指针 ...为了提高程序运行效率,C++11中引入了右值引用右值引用也是别名,但其只能对右值引用 i...
  • 理解右值引用前言问题复现自定义string(CMyString)遇到问题图示理解右值引用什么是右值添加右值引用参数的成员方法结果对比 前言 在之前,我写过一篇: 通过自定义string类型来理解运算符重载。 在文章末尾,我...
  • https://blog.csdn.net/weixin_49199646/article/details/109861756
  • c++11右值引用

    千次阅读 多人点赞 2018-12-20 20:09:12
    从4行代码看右值引用 概述  右值引用的概念有些读者可能会感到陌生,其实他和C++98/03中的左值引用有些类似,例如,c++98/03中的左值引用是这样的: int i = 0; int&amp; j = i;  这里的int&amp;是对...
  • 右值引用的使用场景

    2020-10-23 14:24:37
    与其长篇大论的讲原理,不如先举几个栗子。 1. 函数传参 struct DATA { string value1; string value2; DATA(string v1, ... 上述只是我对右值引用最简单的理解,其他更深入的思考可以参考《Effective Modern C++》
  • C++11 右值引用及性能优化1. 关于左值与右值1.1 左值1.2 右值2. 关于左值引用与右值引用2.1 左值引用2.2 const 左值引用2.3 右值引用2.4 左值引用和右值引用判断函数2.5 万能引用T&&和引用折叠2.6 左值引用...
  • 概述右值引用的概念有些读者可能会感到陌生,其实他和C++98/03中的左值引用有些类似,例如,c++98/03中的左值引用是这样的:int i = 0;int& j = i;这里的int&是对左值进行绑定(但是int&却不能绑定右值...
  • c++——左值、右值、左值引用、右值引用

    万次阅读 多人点赞 2018-10-18 18:17:39
    1、左值和右值 左值(left-values),缩写:lvalues 右值(right-values),缩写:rvalues 直接上官网查,我一向倡导自己去懂得原理,而原理都是老外写的,当然我只是针对c++编程语言这样说。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,796
精华内容 10,718
关键字:

右值引用