• C++右值引用


    左值、右值

    1 左值是表达式结束后依然存在的持久对象

    2 右值是表达式结束后不再存在的临时对象

    简单来说,能取地址的是左值,否则就是右值。

    右值引用的意义

    实现移动语义完美转发

    移动语义:可以用廉价的移动操作来代替昂贵的拷贝操作。

    完美转发:将实参转发到其他的函数,使目标函数接收到的实参与被传递给转发函数的实参保持一致。

    移动语义

    C++11的右值引用和std::move可以实现移动语义,通过减少拷贝操作提升效率

    class A{
    
    public:
        A(): size(0), data(nullptr){}
        A(size_t size): size(size){
            data = new int[size];
        }
    
        A(const A& rhs){
            size = rhs.size;
            data = new int[size];
            for(int i=0; i<size; ++i){
                data[i] = rhs.data[i];
            }
            cout << "Copy constructor" << endl;
        }
    
        A& operator=(const A& rhs){
    
            if(this == &rhs){
                return *this;
            }
    
            delete[] data;
    
            size = rhs.size;
            data = new int[size];
            for(int i=0; i<size; ++i){
                data[i] = rhs.data[i];
            }
            cout << "Copy assignment" << endl;
            return *this;
        }
    
    
        ~A(){
            delete[] data;
        }
    
    private:
        int *data;
        size_t size;
    };

    对于上面的类来说,无论是拷贝构造还是拷贝赋值,都需要将源数据一一拷贝,但是如果源数据在拷贝之后就不需要了,我们直接把源数据拿过来不是省事多了吗?对此我们只需要修改一下指针地址即可,在代码中加入下面的移动构造函数。

    A(A&& rhs){
        data = rhs.data;
        size = rhs.size;
        rhs.size = 0;
        rhs.data = nullptr;
        cout << "Move constructor" << endl;
    }

    于是通过std::move将源数据转化为右值后就会调用相应的移动构造函数。

    int main(){
        A a1 = A(10);
        A a2(std::move(a1));
    }

    完美转发

    首先看第一个例子:

    #include<iostream>
    using namespace std;
    
    int main(){
        int x = 5;
        int& left = x;
        int&& right = std::move(x);
        //cout << &left << endl << &right << endl;
    }

    在上面的代码中,被声明的左值引用和右值引用都是左值,是可以输出它们的地址的。

    然后看二个例子:

    #include<iostream>
    using namespace std;
    
    void func(int& x){
        cout << "左值" <<endl;
    }
    
    void func(int&& x){
        cout << "右值" <<endl;
    }
    
    void test(int&& x){
        func(x);
    }
    
    int main(){
        test(5);
    }

    在上面的代码中,最后的输出是左值,虽然传给 test 函数的是一个右值,然而在调用 func 时,使用的是 x,参数有了名称,联系第一个例子,此时 x 是一个左值。

    如果想要最后的输出是右值,需要使用 std::forward,而 std::forward 中涉及到了引用折叠。

    引用折叠

    对于T&、T&&来说,其引用类型未定,可能是左值引用,也可能是右值引用,需要视传入的实参而定。

    T& &  T& 左值引用的左值引用折叠为左值引用
    T& && T& 右值引用的左值引用折叠为左值引用
    T&& & T& 左值引用的右值引用折叠为左值引用
    T&& && 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::forward 源码

    template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

    根据上述源码,首先通过remove_reference获取 _Tp 的类型 type,然后声明左值引用变量 __t 。

    根据 _Tp 的不同,_Tp 会发生引用折叠:

    1. 当 _Tp 为左值引用时,_Tp折叠为左值引用
    2. 当 _Tp 为右值引用时,_Tp折叠为右值引用

    可以发现当 std::forward 的输入是左值引用时,输出也是左值引用;输入是右值引用时,输出也是右值引用。

    在下面的代码中,使用了 std::forward 之后就会输出右值。

    #include<iostream>
    using namespace std;
    
    void func(int& x){
        cout << "左值" <<endl;
    }
    
    void func(int&& x){
        cout << "右值" <<endl;
    }
    
    void test(int&& x){
        func(std::forward<int>(x));
    }
    
    int main(){
        test(5);
    }
    

      

    std::move 和 std::forward 的区别

    1. std::move 不移动(move)任何东西,也不保证它执行转换的对象可以被移动,它只执行到右值的无条件的转换
    2. std::forward也不转发(forward)任何东西,只有当它的参数被绑定到一个右值时,才将参数转换为右值。
    3. 一个是无条件的,另一个是有条件的,所以有不同的应用场景。

    References:

    1. 一文带你详细介绍c++中的std::move函数
    2. C++ 理解std::forward完美转发
    3. 一文读懂C++右值引用和std::move
    4. 第5章 右值引用,移动语义,完美转发
  • 相关阅读:
    error_reporting(“E_ALL”)和ini_set(“display_errors”, “on”)的区别?
    linux命令awk的详解
    Ubuntu 能PING IP但不能PING主机域名的解决方法
    从github checkout子文件夹
    zuul简单使用
    docker for windows 10 添加阿里云镜像仓库无效问题
    Spring Boot 进行Bean Validate和Method Validate
    JVM调优-GC参数
    Spring Aop: 关于继承和execution target this @annotation
    ReentrantLock原理
  • 原文地址:https://www.cnblogs.com/zyb993963526/p/15933775.html
Copyright © 2020-2023  润新知