<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
移動語意就是使用移動建構函式來構造物件。
我們知道在類中如果存在指標資料成員,那麼我們就一定要寫拷貝建構函式,進行深拷貝
如下所示,就是拷貝建構函式的用法:
#include<iostream> using namespace std; class A { public: int * ptr; A():ptr(new int(0)) { cout<<"constructorn"; }; A(const A & h):ptr(new int(*h.ptr))//拷貝建構函式 { cout<<"copy constructorn"; //deep copy }; ~A() { cout<<"destructorn"; delete ptr; }; }; A getA() { return A(); } int main() { A a=getA(); } //g++ .test.cpp -std=c++11 -fno-elide-constructors
constructor
copy constructor
destructor
copy constructor
destructor
destructor
可以知道上面程式碼中,實際上產生了3個物件,在getA()
函數中,使用預設建構函式產生一個物件,然後將其作為返回值時,又會通過拷貝建構函式產生一個物件,然後在main()
函數中,又會通過拷貝建構函式構造出物件a
,所以總共有3個物件產生,我們這裡的拷貝建構函式是進行的深拷貝,所以就會開闢3塊記憶體.
在C++11中,我們可以使用移動建構函式,對上述程式碼進行優化
#include<iostream> using namespace std; class A { public: int * ptr; A():ptr(new int(0)) { cout<<"constructorn"; }; A(const A & h):ptr(new int(*h.ptr))//拷貝建構函式 { cout<<"copy constructorn"; //deep copy }; A(A && h):ptr(h.ptr)//移動建構函式 { h.ptr=nullptr; cout<<"move constructorn"; } ~A() { cout<<"destructorn"; delete ptr; }; }; A getA() { return A(); } int main() { A a=getA(); } //g++ .test.cpp -std=c++11 -fno-elide-constructors
constructor
move constructor
destructor
move constructor
destructor
destructor
移動建構函式,它是進行的淺拷貝,由於被移動的值會立即進行解構,所以我們不關心它,只需進行淺拷貝,將其開闢的記憶體空間轉讓給別人。上述程式碼中,也會構造出3個物件,但是它們只開闢一塊記憶體空間,這就是移動建構函式的優勢。
總之,我們發現移動建構函式和拷貝建構函式的區別,其實就是深拷貝和淺拷貝的區別,移動建構函式的開銷更小,當然我們關心的是,移動建構函式何時會被觸發?在上面程式碼中就是一個例子,將getA()
中的區域性匿名物件移動給返回值,然後將返回值移動給 main()
中的a
。
這裡我們給出結論:移動建構函式只有在使用右值或右值參照來構造物件時才會呼叫
那麼什麼是右值?
在getA()
中的A()
就是右值,getA()
的返回值也是右值,所以用它們構造物件時,會呼叫移動建構函式
那麼什麼是右值參照?
顧名思義就是右值的參照
在C++11中,我們將值劃分為:左值、右值(分為將亡值和純右值)
左值:可以取地址,有名字的值
右值:不能取地址,沒有名字的值
純右值:運算表示式,如1+2
,或者和物件無關的字面值,如true
,或者非參照的函數返回值,或者lambda
表示式
將亡值:僅和右值參照相關的值,它包括:右值參照的函數返回值T&&
,或者std::move
的返回值,或者被轉換為T&&
型別的函數返回值
注意:不管是純右值還是將亡值,它們的存活時間都很短。不要被將亡值的名稱所迷惑了,其實所以右值的都會即將消亡。
實際上,對於純右值和將亡值的定義很難給出,而且我們也不需要區分它們兩,但是,我們至少可以確定一個值是左值還是右值。
C++98中所提及的參照,在C++11中我們稱之為左值參照,即這個參照只能繫結左值,在C++11中我們提供了一種新的能夠繫結右值的參照,即右值參照。
我們知道左值參照實際是一個變數的別名,右值參照它實際是一個匿名變數的別名
#include<iostream> using namespace std; class A { public: int * ptr; A():ptr(new int(0)) { cout<<"constructorn"; }; A(const A & h):ptr(new int(*h.ptr))//拷貝建構函式 { cout<<"copy constructorn"; //deep copy }; A(A && h):ptr(h.ptr)//移動建構函式 { h.ptr=nullptr; cout<<"move constructorn"; } ~A() { cout<<"destructorn"; delete ptr; }; }; A getA() { return A(); } int main() { A&& a=getA();//右值參照 }
constructor
move constructor
destructor
destructor
在上述程式碼中getA()
的返回值是一個右值,它是一個臨時值,如果我們寫成A a=getA();
,那麼這個臨時值給a
進行移動構造後就會立即被解構,而如果我們使用A&& a=getA();
,那就意味著我們給這個臨時值進行續命,a
就是這個臨時值的別名,所以上述程式碼就會少一個物件的構造。
總之,右值參照就是一種繫結右值的參照,實際上在C++98中,我們所知的const T &
,這樣的參照,也可以繫結右值,他也叫做萬能參照,當他繫結右值的時候它的作用和右值參照是一樣的,只不過這裡的const
是底層的,所以我們不能用其修改右值,所以右值參照系結右值時,可以修改該右值,而當萬能參照系結右值時,我們不可以修改該右值
T& a;//左值參照,只能繫結非常數左值 T&& a;//右值參照,只能繫結非常數右值 const T& a;//萬能參照,它可以繫結一切值,但是它不能修改該值 const T&& a;//和萬能參照功能一樣(一般不使用)
我們仔細來思索一下右值參照的用處,從本質上講,它是給右值進行續命,而從實踐上講,它就是用來移動語意的,但是移動語意的時候,我們希望修改原來的右值(看上面程式碼中的移動建構函式,它實際上修改了右值),所以我們說const T&&
這種是無用的,
我們在學習了C++11中的移動語意和右值參照知識後,我們要深知一個程式設計規矩:
只要類中有指標資料成員,就一定要重寫拷貝建構函式和移動建構函式
看一下下面這段程式碼
#include<iostream> using namespace std; class A { public: int * ptr; A():ptr(new int(0)) { cout<<"constructorn"; }; A(const A & h):ptr(new int(*h.ptr))//拷貝建構函式 { cout<<"copy constructorn"; //deep copy }; A(A && h):ptr(h.ptr)//移動建構函式 { h.ptr=nullptr; cout<<"move constructorn"; } ~A() { cout<<"destructorn"; delete ptr; }; }; A&& getA() { return std::move(A()); } int main() { A&& a=getA(); }
constructor
destructor
在getA()
中的A()
是右值,為什麼還要用std::move
將其轉換為右值參照?因為A()
是一個純右值,右值參照當然可以繫結純右值,但是A()
是一個區域性物件,在函數中返回參照時,我們禁止返回區域性物件的參照,但是當我們使用std::move
後,A()
就會轉換為右值參照型別,這樣子就可以將其作為參照返回。這是一種返回區域性物件參照的特殊方法。
注意,這是一個涉及原則的問題,匿名物件是純右值
class A {}; int main() { A& a=A();//報錯,左值參照無法系結純右值 }
實際上,std::move()
等價於static_cast<T&&>(lvalue)
,即將左值轉換為右值參照。
但是,std::move()
有一個bug
,即被轉化為右值參照的左值,不會被立即解構。
#include<iostream> using namespace std; class A { public: int* ptr; A():ptr(new int(999)){} ~A(){delete ptr;} A(const A& h):ptr(new int(*h.ptr)){} A(A&& h):ptr(h.ptr) { h.ptr=nullptr; } }; int main() { A a; A b(std::move(a)); cout<<*a.ptr<<endl;//報錯 }
上述程式碼就會報錯,因為a
被轉化為右值參照後,b
會呼叫移動建構函式來構造它自己,而在移動建構函式中,它將a.ptr
置空
#include<utility> class A { public: int *ptr; A():ptr(new int(0)){} ~A(){delete ptr;} A(const A& h):ptr(new int(*h.ptr)){} A(A&& h):ptr(h.ptr){h.ptr=nullptr;} }; class B { public: int *ptr; A elem; B():ptr(new int(0)){} ~B(){delete ptr;} B(const B&h):ptr(new int(*h.ptr)),elem(h.elem){} B(B&& h):ptr(h.ptr),elem(std::move(h.elem)){h.ptr=nullptr;} };
注意看,B(const B&h):ptr(new int(*h.ptr)),elem(h.elem){}
中對elem
的初始化使用的是A
的拷貝建構函式,
而B(B&& h):ptr(h.ptr),elem(std::move(h.elem)){h.ptr=nullptr;}
中對elem
的初始化使用的是是A
的移動建構函式. 注意一點,即使這裡我們忘記寫std::move()
也並無大礙,它會自行呼叫拷貝建構函式,當然這也會導致一些開銷,所以在做類開發的時候,在寫類的移動建構函式的時候,總是要記得將類成員move
成右值參照。
如果一個類支援拷貝建構函式和拷貝賦值函數,那麼我們就稱該類具有拷貝語意;同樣的如果一個類支援移動建構函式和移動賦值函數,那麼我們就稱該類具有移動語意。
當然有些類是同時支援移動語意和拷貝語意的。
在C++98中的類基本都是隻具有拷貝語意的,而在C++11中的基本所有類都支援移動語意,特別的,有些類只支援移動語意,而不支援拷貝語意,這種類,我們稱之為資源型類,即資源只能被移動而不能被拷貝,例如智慧指標類unique_ptr
,檔案流ifstream
等都是資源型類,在C++11中,我們可以通過一些工具來判斷一個類是否支援移動語意。
我們看一下下面的程式碼
template <class T> void swap(T& a, T& b) { T tmp(move(a)); a=move(b); b=move(tmp); }
上述程式碼中,如果T
支援移動語意,那麼它就會呼叫移動建構函式和移動賦值函數,而如果T
只支援拷貝語意,那麼它也可以呼叫拷貝建構函式和拷貝賦值函數
我們關於移動語意的另一個話題是:異常。因為如果移動語意沒有完成,卻丟擲異常,那麼可能會導致產生懸掛指標。所以在C++11中我們同樣有std::move_if_noexcept()
函數來檢測,移動建構函式是否用noexcept
修飾。
再討論一個關於編譯器優化的問題,如今c++編譯器已經非常優化了,RVO機制,即所謂返回值優化機制,他能幫你完成類似移動語意的智慧優化,但是要記住,編譯器優化不是完全奏效的,最好還是自己提高程式碼效率。
到此這篇關於C++移動語意詳細介紹使用的文章就介紹到這了,更多相關C++移動語意內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45