移動語義
完成所有權的移交,當拷貝構造和賦值構造時,目標對象的所有權必須移交給我們的新的對象,原始對象將喪失所有權,_p指針將不再指向原來的那個數組;
左值與右值
C原始定義
C++的定義
左值引用與右值引用
左值引用:&
右值引用:&&
class A { public: A() :_n(0), _p(nullptr){} explicit A(int n):_n(n),_p(new int[n]){} A(int n, int *p) :_n(n), _p(p) {} A(A && that); A & operator=(A && that); virtual ~A() { if (_p) { delete[]_p, _p = nullptr; } } public: int & operator[](int i); const int & operator[](int i)const; private: int _n; int *_p; }; A::A(A && that) { //nullptr:C++11預定義的空指針類型nullptr_t的常對象 //可隱式轉換為任意指針類型和bool類型,但不能轉化為整數類型,以取代NULL _n = that._n, _p = that._p, that._n = 0, that._p = nullptr; //*this = that;//此代碼不會調用下面重載的賦值操作符函數 //具名右值引用that在函數內部被當作左值,不是右值 //匿名右值引用才會被當作右值,理論上如此..... //*this = static_cast<A &&>(that);//等價於*this = std::move(that); //上一行代碼可以調用下面重載的移動賦值操作符,但是有可能導致程序崩潰 //因為this指向的本對象可能剛剛分配內存,_p字段所指向的目標數據對象無定義 } A & A::operator=(A && that) { if (_p)//刪除此行代碼可能導致內存洩漏 delete[]_p; //可以測試是否為同一個對象,以避免自身復制操作,但意義不大 _n = that._n, _p = that._p, that._n = 0, that._p = nullptr; return *this; }
移動語義重載
class A { public: A() :_n(0), _p(nullptr) {} explicit A(int n) :_n(n), _p(new int[n]) {} A(int n, int *p) :_n(n), _p(p) {} //可以同時提供拷貝語義與移動語義版本,前者使用常左值引用 //不能修改目標數據對象的值,後者則可以修改 A(const A & that); A(A && that); A & operator =(const A & that);//深拷貝版本 A & operator =(A && that);//移動賦值版本 virtual ~A() { if (_p) { delete[]_p, _p = nullptr; } } public: int & operator[](int i); const int & operator[](int i)const; private: int _n; int *_p; }; int main() { A a(4); for (int i = 0; i < 4; i++) { a[i] = i + 1; } A b(a);//調用拷貝構造函數 b = a; //調用普通賦值版本 //把左值引用轉換為右值引用,否則會調用左值版本 A c(static_cast<A &&>(a));//調用移動構造版本 c = static_cast<A &&>(a);//調用移動賦值版本 return 0; }
左值引用同樣可以實現移動語義
class A { public: A() :_n(0), _p(nullptr) {} explicit A(int n) :_n(n), _p(new int[n]) {} A(int n, int *p) :_n(n), _p(p) {} A(A & that);//重載非常量版本;移動構造語義 A(const A & that);//重載常量版本;深拷貝構造語義 A & operator=(A &that);//重載非常量版本;移動賦值語義 A & operator=(const A & that);//重載常量版本;深拷貝賦值語義 virtual ~A() { if (_p) { delete[]_p, _p = nullptr; } } public: int & operator[](int i) throw(std::out_of_range); const int & operator[](int i) const throw(std::out_of_range); private: int _n; int *_p; }; A::A(A & that) { _n = that._n, _p = that._p, that._n = 0, that._p = nullptr; } A::A(const A & that) { this->_n = that._n; _p = new int[_n]; for (int i = 0; i < _n; i++) { _p[i] = that._p[i]; } } A & A::operator=(A & that) { _n = that._n, _p = that._p, that._n = 0, that._p = nullptr; return *this; } A & A::operator=(const A & that) { this->_n = that._n; if (_p) { delete[]_p; } _p = new int[_n]; for (int i = 0; i < _n; i++) { _p[i] = that._p[i]; } return *this; } //main.cpp int main() { A a1;//缺省構造 const A a2;//缺省構造 A a3(a1);//調用A::A(A &),移動構造 A a4(a2);//調用A::A(const A &),深拷貝構造 //對於非常量,必須轉型為常量才能進行深拷貝 A a5(const_cast<const A &>(a1));//調用A::A(const A &) A a6, a7, a8;//缺省構造 a6 = a1;//調用A::operator=(A &),移動賦值 a7 = a2;//調用A::operator=(const A &),深拷貝賦值 a8 = const_cast<const A &>(a1);//調用A::operator=(const A &) return 0; }
右值引用的意義
右值引用可以使用文字作為函數實際參數
//不接受文字作為實際參數,因無法獲取文字的左值 int f(int &x) { return ++x; } //接受文字作為實際參數,傳遞右值引用 //具名右值引用作為左值,匿名右值引用作為右值 //在函數內部理論如此,但實際上... int f(int && x) { return ++x; } int main() { //錯誤代碼,++操作符的操作數必須為左值 //std::cout << ++10 << std::endl; //可能有問題,傳遞右值引用,但部分編譯器可能將其作為左值 std::cout << f(10) << std::endl;//11? return 0; }
右值引用的意義
避免編寫過多的構造與賦值函數
實現完美轉發