zoukankan      html  css  js  c++  java
  • C++11之 Move semantics(移动语义)(转)

    转https://blog.csdn.net/wangshubo1989/article/details/49748703

    按值传递的意义是什么? 
    当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。 
    而对于我们自定义的类型,我们也许需要提供拷贝构造函数。

    但是不得不说,拷贝的代价是昂贵的。

    所以我们需要寻找一个避免不必要拷贝的方法,即C++11提供的移动语义。 
    上一篇博客中有一个句话用到了:

    #include <iostream>
    
    void f(int& i) { std::cout << "lvalue ref: " << i << "
    "; }
    void f(int&& i) { std::cout << "rvalue ref: " << i << "
    "; }
    
    int main()
    {
        int i = 77;
        f(i);    // lvalue ref called
        f(99);   // rvalue ref called
    
        f(std::move(i));  // 稍后介绍
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    实际上,右值引用注意用于创建移动构造函数和移动赋值运算。

    移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。 
    但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象,而不是进行copy,所以我们可以进行移动。

    换言之,在设计到关于临时对象时,右值引用和移动语义允许我们避免不必要的拷贝。我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。

    右值就是典型的临时变量,并且他们可以被修改。如果我们知道一个函数的参数是一个右值,我们可以把它当做一个临时存储。这就意味着我们要移动而不是拷贝右值参数的内容。这就会节省很多的空间。

     

    下面这个图,很好地说明了拷贝构造函数和移动构造函数的区别。

     

    看明白了吗?

    通俗一点的解释就是,拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制。

    但是上面提到,指针的浅层复制是非常危险的呀。没错,确实很危险,而且通过上面的例子,我们也可以看出,浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间(同时也是b->value指向的空间)

    所以我们可以把上面的拷贝构造函数的代码修改一下:

    复制代码
    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    #include <vector>
    
    using namespace std;
    
    class Str{
        public:
        char *value;
        Str(char s[])
        {
            cout<<"调用构造函数..."<<endl;
            int len = strlen(s);
            value = new char[len + 1];
            memset(value,0,len + 1);
            strcpy(value,s);
        }
        Str(Str &v)
        {
            cout<<"调用拷贝构造函数..."<<endl;
            this->value = v.value;
            v.value = NULL;
        }
        ~Str()
        {
            cout<<"调用析构函数..."<<endl;
            if(value != NULL)
                delete[] value;
        }
    };
    
    int main()
    {
    
        char s[] = "I love BIT";
        Str *a = new Str(s);
        Str *b = new Str(*a);
        delete a;
        cout<<"b对象中的字符串为:"<<b->value<<endl;
        delete b;
        return 0;
    }
    复制代码

    结果为:

     

    修改后的拷贝构造函数,采用了浅层复制,但是结果仍能够达到我们想要的效果,关键在于在拷贝构造函数中,最后我们将v.value置为了NULL,这样在析构a的时候,就不会回收a->value指向的内存空间。

    这样用a初始化b的过程中,实际上我们就减少了开辟内存,构造成本就降低了。

    但要注意,我们这样使用有一个前提是:用a初始化b后,a我们就不需要了,最好是初始化完成后就将a析构。如果说,我们用a初始化了b后,仍要对a进行操作,用这种浅层复制的方法就不合适了。

    所以C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况。

    *************************************************************

    **移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。(关于右值引用大家可以看我之前的文章,或者查找其他资料)。这意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。

    移动构造函数应用最多的地方就是STL中

    给出一个代码,大家自行验证使用move和不适用move的区别吧

    复制代码
    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    #include <vector>
    using namespace std;
    
    class Str{
        public:
            char *str;
            Str(char value[])
            {
                cout<<"普通构造函数..."<<endl;
                str = NULL;
                int len = strlen(value);
                str = (char *)malloc(len + 1);
                memset(str,0,len + 1);
                strcpy(str,value);
            }
            Str(const Str &s)
            {
                cout<<"拷贝构造函数..."<<endl;
                str = NULL;
                int len = strlen(s.str);
                str = (char *)malloc(len + 1);
                memset(str,0,len + 1);
                strcpy(str,s.str);
            }
            Str(Str &&s)
            {
                cout<<"移动构造函数..."<<endl;
                str = NULL;
                str = s.str;
                s.str = NULL;
            }
            ~Str()
            {
                cout<<"析构函数"<<endl;
                if(str != NULL)
                {
                    free(str);
                    str = NULL;
                }
            }
    };
    int main()
    {
        char value[] = "I love zx";
        Str s(value);
        vector<Str> vs;
        //vs.push_back(move(s));
        vs.push_back(s);
        cout<<vs[0].str<<endl;
        if(s.str != NULL)
            cout<<s.str<<endl;
        return 0;
    } 
    复制代码

    说多无语,看代码:

    #include <iostream>
    #include <algorithm>
    
    class A
    {
    public:
    
        // Simple constructor that initializes the resource.
        explicit A(size_t length)
            : mLength(length), mData(new int[length])
        {
            std::cout << "A(size_t). length = "
            << mLength << "." << std::endl;
        }
    
        // Destructor.
        ~A()
        {
        std::cout << "~A(). length = " << mLength << ".";
    
        if (mData != NULL) {
                std::cout << " Deleting resource.";
            delete[] mData;  // Delete the resource.
        }
    
        std::cout << std::endl;
        }
    
        // Copy constructor.
        A(const A& other)
            : mLength(other.mLength), mData(new int[other.mLength])
        {
        std::cout << "A(const A&). length = "
            << other.mLength << ". Copying resource." << std::endl;
    
        std::copy(other.mData, other.mData + mLength, mData);
        }
    
        // Copy assignment operator.
        A& operator=(const A& other)
        {
        std::cout << "operator=(const A&). length = "
                 << other.mLength << ". Copying resource." << std::endl;
    
        if (this != &other) {
            delete[] mData;  // Free the existing resource.
            mLength = other.mLength;
                mData = new int[mLength];
                std::copy(other.mData, other.mData + mLength, mData);
        }
        return *this;
        }
    
        // Move constructor.
        A(A&& other) : mData(NULL), mLength(0)
        {
            std::cout << "A(A&&). length = " 
                 << other.mLength << ". Moving resource.
    ";
    
            // Copy the data pointer and its length from the 
            // source object.
            mData = other.mData;
            mLength = other.mLength;
    
            // Release the data pointer from the source object so that
            // the destructor does not free the memory multiple times.
            other.mData = NULL;
            other.mLength = 0;
        }
    
        // Move assignment operator.
        A& operator=(A&& other)
        {
            std::cout << "operator=(A&&). length = " 
                 << other.mLength << "." << std::endl;
    
            if (this != &other) {
              // Free the existing resource.
              delete[] mData;
    
              // Copy the data pointer and its length from the 
              // source object.
              mData = other.mData;
              mLength = other.mLength;
    
              // Release the data pointer from the source object so that
              // the destructor does not free the memory multiple times.
              other.mData = NULL;
              other.mLength = 0;
           }
           return *this;
        }
    
        // Retrieves the length of the data resource.
        size_t Length() const
        {
            return mLength;
        }
    
    private:
        size_t mLength; // The length of the resource.
        int* mData;     // The resource.
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    移动构造函数 
    语法:

    A(A&& other) noexcept    // C++11 - specifying non-exception throwing functions
    {
      mData =  other.mData;  // shallow copy or referential copy
      other.mData = nullptr;
    }
    • 1
    • 2
    • 3
    • 4
    • 5

    最主要的是没有用到新的资源,是移动而不是拷贝。 
    假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。

    // Move constructor.
    A(A&& other) : mData(NULL), mLength(0)
    {
        // Copy the data pointer and its length from the 
        // source object.
        mData = other.mData;
        mLength = other.mLength;
    
        // Release the data pointer from the source object so that
        // the destructor does not free the memory multiple times.
        other.mData = NULL;
        other.mLength = 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    移动比拷贝更快!!!

    移动赋值运算符 
    语法:

    A& operator=(A&& other) noexcept
    {
      mData =  other.mData;
      other.mData = nullptr;
      return *this;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    工作流程这样的:Google上这么说的:

    Release any resources that *this currently owns. 
    Pilfer other’s resource. 
    Set other to a default state. 
    Return *this.

    // Move assignment operator.
    A& operator=(A&& other)
    {
        std::cout << "operator=(A&&). length = " 
                 << other.mLength << "." << std::endl;
    
        if (this != &other) {
          // Free the existing resource.
          delete[] mData;
    
          // Copy the data pointer and its length from the 
          // source object.
          mData = other.mData;
          mLength = other.mLength;
    
          // Release the data pointer from the source object so that
          // the destructor does not free the memory multiple times.
          other.mData = NULL;
          other.mLength = 0;
       }
       return *this;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    让我们看几个move带来的好处吧! 
    vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:

    std::vector<A> v;
    v.push_back(A(25));
    v.push_back(A(75));
    • 1
    • 2
    • 3

    上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。

    而 当参数为左值的时候,会调用push_back(const T&) 。

    #include <vector>
    
    int main()
    {
        std::vector<A> v;
        A aObj(25);       // lvalue
        v.push_back(aObj);  // push_back(const T&)
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    但事实我们可以使用 static_cast进行强制:

    // calls push_back(T&&)
    v.push_back(static_cast<A&&>(aObj));
    • 1
    • 2

    我们可以使用std::move完成上面的任务:

    v.push_back(std::move(aObj));  //calls push_back(T&&)
    • 1

    似乎push_back(T&&)永远是最佳选择,但是一定要记住: 
    push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。

    最后写一个例子,看看如何使用move来交换两个对象:

    #include <iostream>
    using namespace std;
    
    class A
    {
      public:
        // constructor
        explicit A(size_t length)
            : mLength(length), mData(new int[length]) {}
    
        // move constructor
        A(A&& other)
        {
          mData = other.mData;
          mLength = other.mLength;
          other.mData = nullptr;
          other.mLength = 0;
        }
    
        // move assignment
        A& operator=(A&& other) noexcept
        {
          mData =  other.mData;
          mLength = other.mLength;
          other.mData = nullptr;
          other.mLength = 0;
          return *this;
        }
    
        size_t getLength() { return mLength; }
    
    
        void swap(A& other)
        {
          A temp = move(other);
          other = move(*this);
          *this = move(temp);
        }
    
        int* get_mData() { return mData; }
    
      private:
        int *mData;
        size_t mLength;
    };
    
    int main()
    {
      A a(11), b(22);
      cout << a.getLength() << ' ' << b.getLength() << endl;
      cout << a.get_mData() << ' ' << b.get_mData() << endl;
      swap(a,b);
      cout << a.getLength() << ' ' << b.getLength() << endl;
      cout << a.get_mData() << ' ' << b.get_mData() << endl;
      return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
  • 相关阅读:
    伸展树(SplayTree)的实现
    map的访问
    #pragma warning(disable 4786)
    debian sftp/ssh197
    debian 配置静态ip197
    deepin 安装tar.gz197
    npm构建vue项目197
    linux 常用命令197
    application/force-download 不生效197
    reids 安装197
  • 原文地址:https://www.cnblogs.com/wangshaowei/p/8880394.html
Copyright © 2011-2022 走看看