標籤:c++11 移動語意
按值傳遞的意義是什嗎?
當一個函數的參數按值傳遞時,這就會進行拷貝。當然,編譯器懂得如何去拷貝。
而對於我們自訂的類型,我們也許需要提供拷貝建構函式。
但是不得不說,拷貝的代價是昂貴的。
所以我們需要尋找一個避免不必要拷貝的方法,即C++11提供的移動語意。
上一篇部落格中有一個句話用到了:
#include <iostream>void f(int& i) { std::cout << "lvalue ref: " << i << "\n"; }void f(int&& i) { std::cout << "rvalue ref: " << i << "\n"; }int main(){ int i = 77; f(i); // lvalue ref called f(99); // rvalue ref called f(std::move(i)); // 稍後介紹 return 0;}
實際上,右值引用注意用於建立移動建構函式和移動賦值運算。
移動建構函式類似於拷貝建構函式,把類的執行個體對象作為參數,並建立一個新的執行個體對象。
但是 移動建構函式可以避免記憶體的重新分配,因為我們知道右值引用提供了一個暫時的對象,而不是進行copy,所以我們可以進行移動。
換言之,在設計到關於臨時對象時,右值引用和移動語意允許我們避免不必要的拷貝。我們不想拷貝將要消失的臨時對象,所以這個臨時對象的資源可以被我們用作於其他的對象。
右值就是典型的臨時變數,並且他們可以被修改。如果我們知道一個函數的參數是一個右值,我們可以把它當做一個臨時儲存。這就意味著我們要移動而不是拷貝右值參數的內容。這就會節省很多的空間。
說多無語,看代碼:
#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.\n"; // 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<A> v;v.push_back(A(25));v.push_back(A(75));
上面兩個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&)}
但事實我們可以使用 static_cast進行強制:
// calls push_back(T&&)v.push_back(static_cast<A&&>(aObj));
我們可以使用std::move完成上面的任務:
v.push_back(std::move(aObj)); //calls push_back(T&&)
似乎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;}
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
C++11新特性之 Move semantics(移動語意)