程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++11新特性之 Move semantics(移動語義)

C++11新特性之 Move semantics(移動語義)

編輯:C++入門知識

C++11新特性之 Move semantics(移動語義)


按值傳遞的意義是什麼?
當一個函數的參數按值傳遞時,這就會進行拷貝。當然,編譯器懂得如何去拷貝。
而對於我們自定義的類型,我們也許需要提供拷貝構造函數。

但是不得不說,拷貝的代價是昂貴的。

所以我們需要尋找一個避免不必要拷貝的方法,即C++11提供的移動語義。
上一篇博客中有一個句話用到了:

#include 

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;
}

實際上,右值引用注意用於創建移動構造函數和移動賦值運算。

移動構造函數類似於拷貝構造函數,把類的實例對象作為參數,並創建一個新的實例對象。
但是 移動構造函數可以避免內存的重新分配,因為我們知道右值引用提供了一個暫時的對象,而不是進行copy,所以我們可以進行移動。

換言之,在設計到關於臨時對象時,右值引用和移動語義允許我們避免不必要的拷貝。我們不想拷貝將要消失的臨時對象,所以這個臨時對象的資源可以被我們用作於其他的對象。

右值就是典型的臨時變量,並且他們可以被修改。如果我們知道一個函數的參數是一個右值,我們可以把它當做一個臨時存儲。這就意味著我們要移動而不是拷貝右值參數的內容。這就會節省很多的空間。

說多無語,看代碼:

#include 
#include 

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.
};

移動構造函數
語法:

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

最主要的是沒有用到新的資源,是移動而不是拷貝。
假設一個地址指向了一個有一百萬個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;
}

移動比拷貝更快!!!

移動賦值運算符
語法:

A& operator=(A&& other) noexcept
{
  mData =  other.mData;
  other.mData = nullptr;
  return *this;
}

工作流程這樣的: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;
}

讓我們看幾個move帶來的好處吧!
vector眾所周知,C++11後對vector也進行了一些優化。例如vector::push_back()被定義為了兩種版本的重載,一個是cosnt T&左值作為參數,一個是T&&右值作為參數。例如下面的代碼:

std::vector v;
v.push_back(A(25));
v.push_back(A(75));

上面兩個push_back()都會調用push_back(T&&)版本,因為他們的參數為右值。這樣提高了效率。

而 當參數為左值的時候,會調用push_back(const T&) 。

#include 

int main()
{
    std::vector v;
    A aObj(25);       // lvalue
    v.push_back(aObj);  // push_back(const T&)
}

但事實我們可以使用 static_cast進行強制:

// calls push_back(T&&)
v.push_back(static_cast(aObj));

我們可以使用std::move完成上面的任務:

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

似乎push_back(T&&)永遠是最佳選擇,但是一定要記住:
push_back(T&&) 使得參數為空。如果我們想要保留參數的值,我們這個時候需要使用拷貝,而不是移動。

最後寫一個例子,看看如何使用move來交換兩個對象:

#include 
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. 下一頁:
Copyright © 程式師世界 All Rights Reserved