移動語義

完成所有權的移交,當拷貝構造和賦值構造時,目標對象的所有權必須移交給我們的新的對象,原始對象將喪失所有權,_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;
}
右值引用的意義
避免編寫過多的構造與賦值函數
實現完美轉發