標籤:匿名 臨時對象 地址 size mil null 不難 sap 標準庫
---------------------------------------移動構造--------------------------------------------
傳統的深拷貝深賦值
對於類中,含有指標的情況,要自實現其拷貝構造和拷貝賦值。也就是所謂的深拷貝和深賦值。我想這己經成為一種共識了。
比如如下類:
#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;}
-----------------------------------------模板函數std::move----------------------------------------
雖然不能將一個右值引用直接綁定到左值上,但是我們可以顯式的將一個左值轉換為對應的右值參考型別。我們還可以通過調用一個名為move的新標準庫函數來獲得綁定到左值上的右值引用,此函數定義在untility中。move函數使用了**機制來返回給定對象的右值引用。
int &&rra = rr1; //error: //! error!右值引用不能直接綁定到左值int &&rr = std::move(rr1); // ok!
move調用告訴編譯器:我們有一個左值,但是我們希望像處理一個右值一樣去處理他。
調用move就意味著承諾:除了對rr1賦值或者銷毀它之外,我們將不能再使用它。在調用move之後,我們不能對移動後的來源物件值做任何的假設。
移動構造和移動賦值與std::move