zoukankan      html  css  js  c++  java
  • 右值引用、移动语义

    一、 什么是左值、右值

    首先不考虑引用以减少干扰,可以从2个角度判断:

    • 左值可以取地址、位于等号左边
    • 而右值没法取地址,位于等号右边

    比如:

    int num = 3;
    • num可以通过 & 取地址,位于等号左边,所以num是左值。
    • 3位于等号右边,3没法通过 & 取地址,所以3是个右值。
    再者:

    class Test {
    public:
        Test(int num = 0) {
            _num = num;
        }
    private:
        int _num;
    };
    
    void main(){
    	Test test=Test();
    }
    •  同样的,test可以通过 & 取地址,位于等号左边,所以test是左值。
    • Test()是个临时值,没法通过 & 取地址,位于等号右边,所以Test()是个右值。

    因此,有地址的变量就是左值,没有地址的字面值、临时值就是右值。

    二、什么是左值引用、右值引用

        引用本质是别名,可以通过引用修改变量的值,传参时传引用可以避免拷贝,其实现原理和指针类似。

    1、左值引用

         顾名思义,左值引用=对左值的引用。能指向左值,不能指向右值的

    int num = 3;
    int &ref_num = num; // 左值引用指向左值,编译通过
    int &ref_num = 3; // 左值引用指向了右值,会编译失败

    引用是变量的别名,由于右值没有地址,没法被修改,所以左值引用无法指向右值。

    但是,const左值引用是可以指向右值的:

    const int &ref_num = 3;  // 编译通过

    const左值引用不会修改指向值,因此可以指向右值。

    正是这个原因,函数参数的用法:void Test(const std::string& value);void Test(const std::vector<std::string>& vet)等等。

    2、右值引用

    同理,右值引用=对右值的引用。能指向右值,不能指向左值的。

    int &&right_num = 3; // ok
     
    int num = 3;
    int &&left_num = num; // 编译不过,右值引用不可以指向左值
     
    right_num = 4; // 右值引用的用途:可以修改右值

    三、右值引用能否指向左值

        在C++11中,引入了新特性:std::move

    int num = 3; // num是个左值
    int &left_num = num; // 左值引用指向左值
    int &&right_num = std::move(num); // 通过std::move将左值转化为右值,可以被右值引用指向
     
    cout << num; // 打印结果:3

    本质:

    • 其实std::move语义其本质并非将一个值移动到另一个值,释放原内存。
    • 而是把左值强制转化为右值,让右值引用可以指向左值。
    • 其实现等同于一个类型转换:static_cast<T&&>(lvalue)

    同理,右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值:

    int &&ref_num = 3;
    ref_num = 4; 
    
    等同于以下代码:
    int temp = 30;
    int &&ref_num = std::move(temp);
    ref_num = 40;

    四、左值引用、右值引用本身是左值还是右值?

         被声明出来的左、右值引用都是左值。 因为被声明出的左右值引用是有地址的,也位于等号左边。

    // 形参是个右值引用
    void ChangeValue(int&& right_value) { 
         right_value = 8;
    }
    
    int main() {
    int num = 3; // a是个左值
    int &ref_a_left = num // ref_a_left是个左值引用
    int &&ref_a_right = std::move(a); // ref_a_right是个右值引用
    
    ChangeValue(num); // 编译不过,num是左值,change参数要求右值
    ChangeValue(ref_a_left); // 编译不过,左值引用ref_a_left本身也是个左值
    ChangeValue(ref_a_right); // 编译不过,右值引用ref_a_right本身也是个左值
    
    ChangeValue(std::move(a)); // 编译通过
    ChangeValue(std::move(ref_a_right)); // 编译通过
    ChangeValue(std::move(ref_a_left)); // 编译通过
    
    change(5); // 当然可以直接接右值,编译通过
    
    // 打印这三个左值的地址,都是一样的
    cout << &num << ' ';
    cout << &ref_a_left << ' ';
    cout << &ref_a_right;
    }
    

    最后,std::move会返回一个右值引用int &&,它是左值还是右值呢?

            从表达式int &&ref = std::move(num)来看,右值引用ref指向的必须是右值,所以move返回的int &&是个右值。所以右值引用既可能是左值,又可能是右值吗? 确实如此:右值引用既可以是左值也可以是右值,如果有名称则为左值,否则是右值

    结论:

    1、从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。

    2、右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。

    3、作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。

    代码上的实践

    // std::vector和std::string的实际例子
    int main() {
        std::string str1 = "aacasxs";
        std::vector<std::string> vec;
         
        vec.push_back(str1); // 传统方法,copy
        vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
        vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
        vec.emplace_back("axcsddcas"); // 当然可以直接接右值
    }

    在vector和string这个场景,加个std::move会调用到移动语义函数,避免了深拷贝。

    可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。

    比如:

    moveable_objecta = moveable_objectb; 
    改为: 
    moveable_objecta = std::move(moveable_objectb);

    还有些STL类是move-only的,比如unique_ptr,这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝):

    std::unique_ptr<Test> ptr_test1 = std::make_unique<Test>();
    
    // unique_ptr只有‘移动赋值重载函数‘,参数是&& ,只能接右值,因此必须用std::move转换类型
    std::unique_ptr<Test> ptr_test2 = std::move(ptr_test1); // 编译不通过
    std::unique_ptr<Test> ptr_test2 = ptr_test1; 
    

    std::move本身只做类型转换,对性能无影响。 我们可以在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。

    此外,还有完美转发 std::forward,不过forward多用于模板,后续再补吧。。。

  • 相关阅读:
    MongoDB 搭建可复制群集
    jquery获取json对象中的key小技巧,遍历json串所有key,value
    21-spring学习-springMVC实现CRUD
    java线程--volatile实现可见性
    java线程-synchronized实现可见性代码
    java线程-java多线程之可见性
    java反射--通过反射了解集合泛型的本质
    java反射--方法反射的基本操作
    java反射--获取成员变量信息
    java反射-获取方法信息
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/15027742.html
Copyright © 2011-2022 走看看