zoukankan      html  css  js  c++  java
  • std::move

    一、move的概念

    1、在学习move之前需要知道左值、右值、左值引用、右值引用的概念,见:https://www.cnblogs.com/judes/p/15159463.html

    学习之后需要知道一个重点

    移动构造不进行深拷贝,直接使用右值的资源。【move是用来服务于此重点的】

    2、概念

    move将一个左值强制转化为右值,继而可以通过右值引用使用该值。

    原型:

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

    其中:remove_reference<T>::type是去除T自身可能携带的引用。

    注意1:move是一个常量表达式函数,没有做啥高级的操作,单纯的是将一个左值强制转化为右值;

    注意2:右值其实就是之前“临时变量”的概念;

    注意3:通过move一个变量a,只是返回一个右值,这个右值是根据a转换来的,执行move(a)后,对变量a没有任何改变,这是关键所在,也是让人迷糊的地方;

    注意4:高级的地方是在需要使用到复制对象【类的对象,如果是单纯的基本数据如int、double则无用处,因为这些本身就是右值,也不存在构造】的时候,move用来配合生成右值,右值可用于调用移动构造函数;

    二、帮助理解

    甲有一本电子书,乙需要这本书,有多个方法实现这个场景。

    ①、甲将书拍照,而且只拍目录给乙,乙只能看个大概【对应浅拷贝】;

    ②、甲将书每一页都手抄下来,抄完之后给乙【对应深拷贝,深拷贝构造函数都是const引用,但这里实际上应该是乙来抄,因为是在乙的构造函数里深拷贝】;

    ③、甲将电子书复制一个副本【右值】发给乙,乙的任何操作不会影响甲的电子书【对应移动构造,输入右值】

    综上我们引入move的目的流程就是:

    我们需要右值【因为最后一步的需求】--------->move提供左值转右值强制转换功能--------->用于服务移动构造函数--------->移动构造里里直接将右值对象的资源给本对象。

    三、矛盾点

    如果使用普通浅拷贝,那本对象只能与原来对象共用资源;

    如果使用深拷贝,那本对象会复制原对象所有资源,会消耗性能;

    解决此矛盾点:

    通过原对象生成一份右值,通过此右值来初始化本对象,此本对象不需要深拷贝,浅拷贝即可,并且不影响原对象资源。而这个右值初始化对象操作我们一般放在移动构造里实现。

    四、使用

    1、用右值初始化对象

    ①、使用普通构造

    #include <iostream>
    #include <vector>
    
    class B
    {
    public:
        B() { }
        B(const B&) {  }
    };
    
    class A
    {
    public:
        A(): m_b(new B()) { std::cout << "A Constructor" << std::endl; }
        A(const A& src) :
            m_b(new B(*(src.m_b)))
        {
            std::cout << "A Copy Constructor, new something" << std::endl;
        }
        A(A&& src) noexcept :
            m_b(src.m_b)
        {
            src.m_b = nullptr;
            std::cout << "A Move Constructor, don't new anything" << std::endl;
        }
        ~A() { delete m_b; }
    
    private:
        B* m_b;
    };
    
    static A getA()
    {
        A a;
        return a;
    }
    
    int main()
    {
        A a = getA();
        A a1(a);
        //A a1(std::move(a));
        return 0;
    }

    调用了拷贝构造函数,实现了深拷贝,耗费了性能,打印:

     ②、使用移动构造

    #include <iostream>
    #include <vector>
    
    class B
    {
    public:
        B() { }
        B(const B&) {  }
    };
    
    class A
    {
    public:
        A(): m_b(new B()) { std::cout << "A Constructor" << std::endl; }
        A(const A& src) :
            m_b(new B(*(src.m_b)))
        {
            std::cout << "A Copy Constructor, new something" << std::endl;
        }
        A(A&& src) noexcept :
            m_b(src.m_b)
        {
            src.m_b = nullptr;
            std::cout << "A Move Constructor, don't new anything" << std::endl;
        }
        ~A() { delete m_b; }
    
    private:
        B* m_b;
    };
    
    static A getA()
    {
        A a;
        return a;
    }
    
    int main()
    {
        A a = getA();
        //A a1(a);
        A a1(std::move(a));
        return 0;
    }

    调用了移动构造函数,只需要浅拷贝,打印:

     2、高效交换值

    template <class T>
    void MoveSwap(T & a, T & b)
    {
        T tmp = move(a);  //std::move(a) 为右值,这里会调用移动构造函数
        a = move(b);  //move(b) 为右值,因此这里会调用移动赋值运算符
        b = move(tmp);  //move(tmp) 为右值,因此这里会调用移动赋值运算符
    }
     
    template <class T>
    void Swap(T & a, T & b) 
    {
        T tmp = a;  //调用复制构造函数
        a = b;  //调用复制赋值运算符
        b = tmp;  //调用复制赋值运算符
    }

    五、实际的作用【纯个人理解】

    ①、对于单个的或指定数量的对象复制处理中,不需要引入这种晦涩难懂的玩意儿,好处是可以理解左值、右值,也就是以前的临时变量概念【现在这个概念被淡化了,右值概念来替代】,减少临时变量的生成,写多了反而让队友看不懂。【本人学习move这概念花了2周左右,因为其牵扯了左右值、移动构造、深浅拷贝等概念,实际上的move就是个常量表达式】

    ②、批量的复制,如自定义一个类,这个类存储的是我们需要处理任务的最小单元,也就是生产者消费者模式里的单个任务,里面的属性可能有指针,指针涉及到初始化。当把多个这样的类对象放在类似于vector这样的连续性容器里时,一旦在倒数n处的位置进行push或insert时,此位置之后的所有对象都得往后移动一位,即产生nx1次拷贝,时间复杂度是O(n);针对这种情况,我们就可以为类设计一个移动构造函数,并实现资源的重新指向,如此之后在进行push时,我们每次都是用前面一个对象的右值来初始化下一个位置的对象,时间复杂度为O(1)【考虑首尾的话就是O(2)?】,效果不言而喻。

    PS:

    1、右值引用可以用来改变右值,它是一个左值

    class A{
       ..... 
    };
    A a;
    A b = std::move(a);//这里并没有看到右值引用,单纯的用右值来调用移动构造初始化b,移动构造的入参就是右值引用

    std::move(a)是生成一个右值,继而可以用右值引用,右值转化为右值引用是通过函数入参或直接声明来实现的:

    方式1:
    A&& rf = std::move(a);
    
    方式2:
    void fun(A&& rf)
    {
        rf.xxxx = nullptr;
    }

    其中方式2可以是普通函数,但更常见在于移动构造函数、移动赋值函数【我更喜欢统称移动构造函数】

    所谓的改变右值,一般就是将这个右值里指针【一般有指针的时候才会触发这种情景】置为空,这就达到了将右值的资源全转为本对象

    2、move不改变原始值,但一般跟原始值即将不存在的情况使用

    如函数返回值初始化对象、vector的push操作、两值交换等




    长风破浪会有时,直挂云帆济沧海!
    可通过下方链接找到博主
    https://www.cnblogs.com/judes/p/10875138.html
  • 相关阅读:
    软工1816 · 第四次作业
    Alpha 冲刺 (3/10)
    Alpha 冲刺 (2/10)
    Alpha 冲刺 (1/10)
    软工 第七次作业
    软工实践第八次作业
    软工实践第六次作业——团队选题报告
    软工实践第二次结对作业(作业五)
    软工第四次作业
    软工实践第三次作业
  • 原文地址:https://www.cnblogs.com/judes/p/15159454.html
Copyright © 2011-2022 走看看