------------------------------------移動構造------------------------------------------
傳統的深拷貝深賦值
對於類中,含有指針的情況,要自實現其拷貝構造和拷貝賦值。也就是所謂的深拷貝和深賦值。我想這己經成為一種共識了。
比如如下類:
#include <iostream> using namespace std; class HasPtrMem { public: HasPtrMem():_d(new int(0)){ cout<<"HasPtrMem()"<<this<<endl; }
HasPtrMem(const HasPtrMem& another) :_d(new int(*another._d))
{ cout<<"HasPtrMem(const HasPtrMem& another)"<<this<<"->"<<&another<<endl; }
~HasPtrMem(){ delete _d; cout<<"~HasPtrMem()"<<this<<endl; }
int * _d; }; HasPtrMem getTemp() { return HasPtrMem(); } int main(int argc, char *argv[]) {
// HasPtrMem a; // HasPtrMem b(a); // cout<<*a._d<<endl; // cout<<*b._d<<endl; HasPtrMem&& ret = getTemp(); return 0; }
上面的過程,我們己經知曉,ret 作為右值引用,引用了臨時對象,由於臨時對象是待返回對象的復本,所以表面上看起來是,待返回對象的作用域擴展了,生命周期也延長了。
從右值引到移動構造
前面我們建立起來了一個概念,就是右值引用。用右值引用的思想,再來實現一下拷貝。這樣,順便把臨時對象的問題也解決了。
#include <iostream> using namespace std; class HasPtrMem { public: HasPtrMem():_d(new int(0)){ cout<<"HasPtrMem()"<<this<<endl; } HasPtrMem(const HasPtrMem& another) :_d(new int(*another._d)){ cout<<"HasPtrMem(const HasPtrMem& another)" <<this<<"->"<< &another<<endl; }
HasPtrMem(HasPtrMem &&another) { cout<<this<<" Move resourse from "<<&another<<"->"<< another._d <<endl; _d = another._d; another._d = nullptr; } ~HasPtrMem(){ delete _d; cout<<"~HasPtrMem()"<<this<<endl; } int * _d; }; HasPtrMem getTemp() { return HasPtrMem(); } int main(int argc, char *argv[]) { HasPtrMem a = getTemp(); return 0; }
移動構造
如下是,移動構造函數。我們借用臨時變量,將待返回對象的內容“偷”了過來。
移動構造充分體現了右值引用的設計思想,通過移動構造我們也在對象層面看清了右值引用的本質。從而對於普通類型右值引用內部是怎樣操作的的也就不難理解了。
//移動構造
HasPtrMem(HasPtrMem &&another) { cout<<this<<" Move resourse from "<<&another<<"->"<< another._d<<endl; _d = another._d; another._d = nullptr; }
再來看一下拷貝構造函數,我們對比一下區別:
HasPtrMem(const HasPtrMem& another) :_d(new int(*another._d)){ cout<<"HasPtrMem(const HasPtrMem& another)" <<this<<"->"<< &another<<endl; }
移動構造相比於拷貝構造的區別,移動構造通過指針的賦值,在臨時對象析構之前,及時的接管了臨時對象在堆上的空間地址。
關於默認的移動構造函數
對於不含有資源的對象來說,自實現拷貝與移動語義並沒有意義,對於這樣的類型 而言移動就是拷貝,拷貝就是移動。
拷貝構造/賦值和移動構造/賦值,必須同時提供或是同時不提供。才能保證同時俱有拷貝和移動語義。只聲明一種的話,類只能實現一種語義。
只有拷貝語義的類,也就是 C++98 中的類。而只有移動語義的類,表明該類的變量所擁有的資源只能被移動,而不能被拷貝。那么這樣的資源必須是唯一的。只有移動語義構造的類型往往是“資源型”的類型。比如智能指針,文件流等。
效率問題
#include <iostream>
using namesapce std;
class Copyable { public: Copyable(int i) :_i(new int(i)) { cout<<"Copyable(int i):"<<this<<endl; } Copyable(const Copyable & another) :_i(new int(*another._i)) { cout<<"Copyable(const Copyable & another):"<<this<<endl; } Copyable(Copyable && another) { cout<<"Copyable(Copyable && another):"<<this<<endl; _i = another._i; } Copyable & operator=(const Copyable &another) { cout<<"Copyable & operator=(const Copyable &another):"<<this<<endl; if(this == & another) return *this; *_i=*another._i; return *this; } Copyable & operator=(Copyable && another) { cout<<"Moveable & operator=(Moveable && another):"<<this<<endl; if(this != &another) { *_i = *another._i; another._i = NULL; } return * this; } ~Copyable() { cout<<"~Copyable():"<<this<<endl; if(_i) delete _i; } void dis() { cout<<"class Copyable is called"<<endl; } void dis() const { cout<<"const class Copyable is called"<<endl; } private: int * _i; }; void putRRValue(Copyable && a) { cout<<"putRRValue(Copyable && a)"<<endl; a.dis(); } void putCLValue(const Copyable & a) { cout<<"putCRValue(Copyable & a)"<<endl; a.dis();//error! } //const T&和T&&重載同時存在先調用誰? void whichCall(const Copyable & a) { a.dis(); } void whichCall(Copyable && a) { a.dis(); } int main(int argc, char *argv[]) { // Copyable rrc = getCopyable(); cout<<"調用移動構造"<<endl; Copyable a =Copyable(2);//匿名對象/臨時對象優先調用右值引用 構造-右值構造 cout<<"調拷貝構造"<<endl; Copyable ca(a); cout<<"直接構造右值"<<endl; Copyable && rra =Copyable(2); cout<<"=================================="<<endl; //右值引用與const引用。 效率是否一樣? cout<<"右值引用傳參"<<endl; putRRValue(Copyable(2)); cout<<"Const 引用傳參"<<endl; putCLValue(Copyable(2)); cout<<"----------------------"<<endl; //優先調用哪種重載? T&& 還是 const T&? whichCall(Copyable(2)); //這個沒什么好糾結的!T&&的出現就是了解決 const T &接受匿名/臨時對象后,不能調用非cosnt函數的問題。 return 0; }
在接收一個右值的情況下,T&& 與 const T& 在效率別無二致,關鍵的是,T&&滿足了我們non-const對象的使用情況。
--------------------------------------------------移動構造-----------------------------------------------
#include <iostream> using namespace std; class Copyable { public: Copyable(int i) :_i(new int(i)) { cout<<"Copyable(int i):"<<this<<endl; } Copyable(const Copyable & another) :_i(new int(*another._i)) { cout<<"Copyable(const Copyable & another):"<<this<<endl; } Copyable(Copyable && another) { cout<<"Copyable(Copyable && another):"<<this<<endl; _i = another._i; } Copyable & operator=(const Copyable &another) { cout<<"Copyable & operator=(const Copyable &another):"<<this<<endl; if(this == & another) return *this; *_i=*another._i; return *this; } Copyable & operator=(Copyable && another) { cout<<"Moveable & operator=(Moveable && another):"<<this<<endl; if(this != &another) {
delete _i; _i = another._i; } return * this; } ~Copyable() { cout<<"~Copyable():"<<this<<endl; if(_i) delete _i; } void dis() { cout<<"class Copyable is called"<<endl; } void dis() const { cout<<"const class Copyable is called"<<endl; } int * _i; }; int main(int argc, char *argv[]) { Copyable a(1); Copyable b(2); cout<<"-----copyassin start------"<<endl; a = b; //拷貝賦值 cout<<"-----moveassin start------"<<endl; a = Copyable(3); //移動賦值 getchar(); return 0; }
移動賦值,拷貝賦值的區分,前者是在兩個對象都存在的情況下,一個給另一個賦值。后者是拿一個即將構造的對象給一個已存在的對象賦值。
效率問題:移動賦值也是將一個將亡值的資源直接接管了過來,無需再去申請新的內存。尤其是對象中所含堆上資源比較大的情況下,在效率上的體現是非常高的。
移動構造,移動賦值,是有極高效率的。右值引用的價值也體現在這里。主題的思想是把把將亡值的資源接管過來。而不用自己去申請空空間。
------------------------------------模板函數std::move----------------------------
雖然不能將一個右值引用直接綁定到左值上,但是我們可以顯式的將一個左值轉換為對應的右值引用類型。我們還可以通過調用一個名為move的新標准庫函數來獲得綁定到左值上的右值引用,此函數定義在untility中。move函數使用了**機制來返回給定對象的右值引用。
int &&rra = rr1; //error: //! error!右值引用不能直接綁定到左值
int &&rr = std::move(rr1); // ok!
move調用告訴編譯器:我們有一個左值,但是我們希望像處理一個右值一樣去處理他。
調用move就意味着承諾:除了對rr1賦值或者銷毀它之外,我們將不能再使用它。在調用move之后,我們不能對移動后的源對象值做任何的假設。
