想要搶先看後面的章節?打賞本文10元,即可獲得帶插圖全本下載地址!

打賞完成記得私信我哦 :p

12.2.2 引用計數:shared_ptr為什麼這麼聰明?

這裡大家一定會問,shared_ptr為什麼這麼聰明,能夠知道它所指向的內存資源還有沒有人在使用?從而可以在沒人使用的情況下自動釋放這塊內存資源。原因其實很簡單,就像我們想要知道哪些人欠我們的錢一樣,我們總是把欠我們錢的人記錄在一個小本本上。如果某個人欠了我們的錢,我們就把他記錄在小本本上,如果他還錢了,我們就把他從小本本上刪去。這樣,我們一看小本本就知道誰欠我的錢了。shared_ptr對它所指向的內存資源的管理也是同樣的道理,在它的背後也有一個記賬的小本本——引用計數。當新增一個shared_ptr對該資源進行管理時,也就是新增一個指向此資源的shared_ptr時,它就會將該內存資源的引用計數加1;反之,當減少一個shared_ptr對該資源進行管理時,就會將該內存資源的引用計數減1;如果該內存資源的引用計數變為0,則說明沒有任何指針對其進行管理,就自動調用delete操作符釋放這塊內存資源。例如,下面這段程序展示了引用計數的工作機制:

// shared_ptr定義在頭文件<memory>中
#include <memory>
#include <iostream>
using namespace std;

int main()
{
shared_ptr<int> spFirst( new int );
// 這時,只有spFirst這個指針指向這塊int類型的內存資源,
// 所以這時它的引用計數是1
cout<<"當前引用計數: "<<spFirst.use_count()<<endl;
{
// 創建另外一個shared_ptr,並用spFirst對其進行賦值,
// 讓它們指向同一塊內存資源
shared_ptr<int> spCopy = spFirst;
// 因為spFirst和spCopy都指向這一塊內存資源,
// 所以這一資源的引用計數增加為2
cout<<"當前引用計數: "<<spFirst.use_count()<<endl;
}
// 當超出spCopy的作用域,spCopy結束其生命周期,
// 這塊內存資源的引用計數減1而重新變為1 cout<<"當前引用計數: "<<spFirst.use_count()<<endl;

// 當程序最終結束執行返回,spFirst指針也結束其生命周期後,
// 從此沒有任何指針指向此內存資源,引用計數減為0,內存資源自動得到釋放
return 0;
}

從這段代碼的輸出中我們可以清楚地看到,一塊內存資源的引用計數,也就是指向這塊內存資源的shared_ptr的個數。在程序的一開始,我們用new操作符申請了一塊int類型的內存,然後用返回的內存地址作為shared_ptr的構造函數的參數創建了spFirst這個shared_ptr對象。這時只有spFirst指向這塊內存資源,所以用use_count()函數獲得的引用計數是1。當程序進入「{}」包圍形成的代碼塊之後,spFirst被賦值給了spCopy,這就意味著增加了一個shared_ptr指向這塊內存資源,所以它的引用計數從1 增加為2。當程序退出「{}」包圍形成的代碼塊,也就是退出spCopy的作用域之後,spCopy被銷毀。這時又只剩下spFirst指向這塊內存資源,所以它的引用計數也從2減為1。當main()函數執行完畢退出後,spFirst也被銷毀,這時就沒有shared_ptr指向這塊內存資源了,它的引用計數從1減為0。當一塊內存資源的引用計數減少為0時,則意味著沒有任何shared_ptr指向這塊內存資源,也就無法對其進行訪問。既然誰都無法再次訪問到這塊內存資源,那就表示它已經使用完畢,shared_ptr會使用delete操作符自動釋放這塊內存資源。 整個過程如下圖12-2所示:

圖12-2 引用計數的變化

就這樣,通過一個簡單的引用計數,shared_ptr就知道了當前還有多少人在使用它所管理的內存資源,從而也就可以在使用者減少為0的時候,聰明地自動釋放掉這塊內存資源。程序員只需要使用new操作符直接申請內存資源並交給shared_ptr管理,然後就盡情地享受通過指針直接操作內存資源所帶來的快樂就好了。至於那些讓人頭疼的內存資源何時何地釋放的問題,就交給聰明的shared_ptr去care吧,它會搞定一切的。

知道更多:用make_shared()函數簡化shared_ptr的創建

雖然shared_ptr的使用,可以讓某塊內存資源在不再被使用的時候自動得到釋放,省去了使用delete操作符釋放內存資源的麻煩,但是我們依然要使用new操作符來申請內存資源,依然擺脫不了危險的對內存的直接操作。有沒有一種方法,可以幫助我們走完這最後一步,可以讓標準庫完全代替程序員來進行內存的申請與釋放呢?

答案當然是肯定的。C++11的標準庫中提供了一個make_shared()函數,我們可以使用它來完成內存資源的申請並直接交給shared_ptr進行管理。make_shared()是一個函數模板,它以需要創建的對象類型為模板參數,而它返回的正是指向這個新創建對象的shared_ptr。如果這個類的構造函數需要參數,它還可以帶有不定個數的參數,並在執行的時候將它們轉遞給這個類的構造函數作為其參數。例如:

// 使用int作為模板參數,make_shared()將創建一個int對象,
// 並返回一個指向這個對象的shared_ptr指針
shared_ptr<int> spFirst = make_shared<int>();
// 使用string作為模板參數,make_shared()將創建一個string對象,
// make_shared()的參數將傳遞給string類的構造函數作為其參數,
// 我們最終得到的shared_ptr指向一個string對象,
// 而這個string對象的內容是「WangGang」
shared_ptr<string> spName = make_shared<strng>("WangGang");

make_shared()的使用,省去了程序員使用new操作符直接申請內存的危險操作,將內存資源的申請和釋放完全交給標準庫去完成,這樣不僅省去了程序員的工作,還可以保持代碼風格的一致性,同時也使得代碼具備了異常安全性。

推薦閱讀:

相关文章