【Modern C++ #1】 C++ smart pointers (auto_ptr, unique_ptr, shared_ptr, weak_ptr) 與 make_unique, make_shared 筆記

➣ Reading Time: 24 minutes

前言

此文章目前尚未整理完成,如需學習完整內容可參考隨附的 reference,或自行 google 搜尋
但因為作者要整理的筆記太多,如果想早點看到整理後的文章,可以下方留言「期待整理」之類的… 我會努力找時間優先整理!

Modern C++ 是 C++ 在 2011 年後,嘗試將 C++ 透過制定一些新的標準,
讓這個老語言有了一些比較接近現代語法的功能,有些人會稱之為 Modern C++
(C++11,就是指 2011 版的 C++)

對於我自己的感覺來說,C++ 怎麼在 C++11 以後越來越像 python XDD

C++ 就我自己學習到現在,我依然覺得 C++ 在學習的門檻的確實很高,

這裡比較對象是我另外學習的 python

不過也因為 C++ 對於各方面的定義較為嚴謹(相比 python),
一直都是對於各細節要求謹慎的工程師,最終會更喜歡的語言

我自己一開始也是喜歡 Python 的快速與方便,
但對於一些細節希望要求更謹慎時,最後還是跑回來寫 C++ 了
如果要舉例的話,python 當你看到「3」你會想到什麼?
有可能是 int,也有可能是 string,這就是可能會出問題的細節,C++ 對這個要求在定義時就嚴謹。
(…雖然後來出了一個 auto)

但如果要快速開發一個腳本我還是會寫 python 哈哈哈哈哈

想了解這邊的歷史推薦這系列的鐵人賽文章,寫得非常不錯:

smart pointer 想要解決的問題

對於 C++ 頭痛的,指標絕對榜上有名,
最常見的就是忘記 delete 造成可能 memory leak 的問題,
smart pointer 被發明出來的目的,能改善不少事情
大致上他可以處理以下的幾種情況 (相比傳統的 raw pointer):

1. 純宣告的時候,我們不清楚 raw pointer 是 object 或是 array

直接看以下例子吧,我們沒辦法很直接的分出來 object 與 array ,
所代表的到底是 object 還是 array

MyClass *obj = new MyClass();
delete obj;

int *array = new int[size];
delete [] array;

如果使用 smart pointer,就會變成

std::unique_ptr<MyClass> obj(new MyClass);

std::unique_ptr<int[]> array (new int[size]);

2. raw pointer 只負責宣告,delete (destory) 這件事情我們要自己負責

new 負責把物件建構出來,但 delete 這件事情要靠我們自己完成,
這很考驗我們對於程式的掌握度,
如果在錯誤的地方不小心先 del 物件,
segmentation fault 就會來了

  • 來看一點例子:

出自:DAY 2:指標是功能還是臭蟲?兼談 Smart Pointer(拜託不要翻成「聰明指標」)的必要性

上面的系列文寫的非常好,有機會的話也非常推薦大家去看!!!

MyClass* obj = new MyClass();

if (something when wrong)
    return;

// use obj to do someting
delete obj;

我們很容易的看出來,如果當提早觸發了 if return,
我們特別寫的 delete 就失效了,
這就是應該要注意卻也很容易犯的錯誤。

還有一些常見的邏輯錯誤,free() 與 delete 混用,
這邊我們不細講,但簡單說 malloc 對應的才是 free,
而 new 對應的是 delete 概念並不相同。

3. 同樣的,我們也不知道我們要 delete 時,使用的對象是 object 或 array,因此要 delete object 或 delete [],我們必須自己搞清楚

從上述例子中,我們可以看出 delete object 或 delete array 實作方式是不同的,
使用以往的方式我們必須自己保證這部份使用的正確性。

  • 使用錯,就會報錯:
delete obj;
delete [] array;

4. 既然我們要自己手動 delete,如何確保我們只做了一次 delete?

從上面的例子來說,看起來 delete 這東西我們可以自己寫,
那萬一我們多寫幾個,會不會造成意外的錯誤或資源的浪費?

我不知道XD 但感覺就很不好

5. 可能會有 Dangling pointer, 也就是說「物件已經被破壞,但仍有 pointer 指向他的情況」

Dangling pointer 相信大家在學習時多少可能都有被提醒使用指標時務必要注意,
不然 segmentation fault 可能又會來找你泡茶,

簡單來說就是一個 pointer 指向了一個「已經被破壞的物件」

  • 我們可以看下面的例子:

出自:What is a dangling pointer?

Class *object = new Class();
Class *object2 = object;

delete object;
object = nullptr;
// now object2 points to something which is not valid anymore

我們可以看到 object2 指向的 object 當他被 delete 後,
object2 指向一個未知的東西
(結果有可能正確/錯誤,但正確也只是我們運氣好,該記憶體位置還沒有被洗掉)

這就有可能會造成我們難以預期的問題,不過現在的 complier 通常都會提醒報個 warning…
不過還是會給編譯過的!!不要看編譯過了,就忽略 warning 那是真的在警告阿!!!

  • 我們來看另外一個例子,這次是 local variable 作用域的問題
Object *method() {
  Object object;
  return &object;
}

Object *object2 = method();
// object2 points to an object which has been removed from stack after exiting the function

這個問題反應出的事情是,我們的 function 作用域只有在 function 裡面,「代表在 function 裡面的都是 Local variable,而離開 function 後他就會有可能被洗掉!!!

一樣的,我們依然有可能會得到 正確/錯誤 的結果,但正確也只是我們運氣好,該記憶體位置還沒有被洗掉,遲早會出現難以預期的問題,而且可能會在我們沒有知覺的情況下發生!!!這還不危險嗎!!!

我自己當初學指標也是因為這以上種種麻煩,就覺得指標超煩 (雖然到現在還是這麼覺得),憚煩歸煩,為了達到完整的記憶體控制,我們必須把這些細節都搞清楚,這也是 C++ 指標最精華的地方

(當你控制的不再是物件,而是記憶體,這能讓我們對程式的掌握性更高)

簡介四個 smart pointer

smart pointer 總共有四種,

  • std::auto_ptr
  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

因為是實用取向的筆記,我們不會針對目前已經幾乎不用的 auto_ptr 有太多介紹,
可以當作他是歷史過程的產物就好,目前主要功能都已經被 unique_ptr 取代。

Smart pointer 的出現,就是為了協助我們管理上面的問題,他幫聰明的幫我們決定 pointer 被 delete 的時間與位置,使我們在開發上不用再去擔心上面的問題。

具體上來說,我們可以想像在實作上,smart pointer 替我們監控了每一個變數的使用區間,因此可以準確的代替我們決定好每一個 resource 應該被 destory 的時間。

這裡寫的比較淺,實際上定義會更嚴謹,有興趣的朋友可以再自行去研究

另外,有沒有使用 smart pointer 不能使用,或使用 raw pointer 會更好的情況?

答案是有的,但這個我們之後再談,大部分的情況,smart pointer 都能夠好好的取代我們 raw pointer 的功能。

std::unique_ptr

unique_ptr 正如其名,他替我們保證我們在使用 pointer 的過程中,永遠都只有一個 ptr 會指向我們的 object,
如果我們嘗試進行 copy pointer 被 compiler 禁止,這樣我們就可以避免前一章節所說的 dangling pointer 的危險。

基本上 unique_ptr 在大多數情況都建議取代以往的 raw pointer,
我們就不需要在煩惱何時 delete ,或該如何 delete 的問題。

簡單來說:

  • 對於同一個 object,禁止 copy,只可使用 move (只有一個是 owner)
  • unique_ptr 所指向的 resource,「delete raw pointer」的功能已經實作在 unique_ptr 裡面,我們只需使用,不用擔心何時 delete 的問題。
  • 當 move 發生時,例如 src move 至 dst,dst 得到 resource,而 src 會變成 null ptr
  • 無法將 new 轉換為 unique_ptr,因為程式也沒有辦法保證你有沒有把 new 傳給了兩個 unique_ptr,直接擋掉
  • unique_ptr 容易轉換為 shared_ptr (下方會提)

std::unique_ptr 範例

MyClass *obj = new MyClass();
delete obj;

我們不用「MyClass *」,改用 「std::unique_ptr」取代

std::unique_ptr<MyClass> obj(new MyClass);

make_unique (C++ 14)

make_unique 與 make_shared ,相比使用 new 的情況,
是對配置 constructor 相對友善的。

沒有特殊情況的話,建議都以 make_unique 與 make_shared 代替 new

std::shared_ptr

shared_ptr 與 unique_ptr 最大的差別,正如同他名字的 unique 與 shared 的差別,
而如果我們使用的指標,有必要出現兩個以上的指標同時需要指向同一個 object 的情況,就會需要使用 shared_ptr

而為了妥善管控資源的 destory,就有了「reference count」的這個概念,
resource’s reference count,會隨著 shared_ptr 被建立時一同被建立,
負責計算目前的 resource 被幾個 pointer 指向當中。

但也因為 reference count,我們在使用 shared_ptr 會使用兩倍的資源 (reference count 本身也是個 pointer)

  • 此外,此計數為 atomic,因此 multi thread IO 時,效能會比較差

control block

建立 shared_ptr 時,control block 在以下情況會同時被建立,
但要注意「不同 shared_ptr 之間無法知道是否有其他人已經建立了 control block」

  • create from raw pointer (new)
  • create from unique_ptr (move ownership from unique_ptr to shared_ptr)
  • create from make_shared

raw pointer (new) -> shared_ptr

其中如果我們從「一個 raw pointer」 建立「兩次 shared_ptr」,程式會壞掉。
因為這也代表著不同的 control block 指向同一個 pointer,會被 release 兩次。

這問題有兩種解法,一種是 make_shared ,我們下方會提到,
另外一種是把 new 就直接放在 share_ptr 的 constructor,這樣可以避免這個風險。

unique_ptr -> shared_ptr (move ownership)

make_shared -> shared_ptr

make_shared 是一個客製化的 constructor,
我們正常情況下不會取得他的 raw pointer (除非硬要),這可以巧妙的迴避這個風險。

使用 make_shared 的 constructor,可以避免一些動態配置的負擔

std::shared_ptr 其他特性

  • unique_ptr -> shared_ptr 是單向的,不可逆向轉換
  • shared_ptr 不能使用 array 的方式操作,有需求可使用 vector 取代

std::weak_ptr

Reference

shared_ptr

unique_ptr

modern C++

⭐C++ 基礎用法 相關文章整理⭐:
1.【C++】C++ compile 程式碼 使用 c++ 11 與使用相關的 package
2.【C++】C/C++ 顯示資料的類別 (type) sample code (內含範例程式碼) print C data type, cout C++ data type, get variable type in c++
3.【C++】C++ 複製 2D array的方法 copy 2d array memcpy sample code (內含範例程式碼)
⭐Modern C++ ⭐:
⭐C++ 字串處理相關文章整理⭐:
1.【C++】字串 char string stringstream 相關用法總整理 (內含範例程式碼) 與利用 sprinf, snprinf assign 值的方法
2.【C++】字串 char string stringstream 「轉換」用法總整理 (內含範例程式碼)
3.【C】printf, fprintf, sprintf, snprintf 相關用法總整理 (內含範例程式碼)
4.【C++】C++ String 用法 連接兩個 String c++ string concat
⭐C++ 系統偵測相關文章整理⭐:
1.【C++】C++ 利用 dirent.h 計算資料夾的檔案數量 count files sample code (內含範例程式碼)
2.【C++】C++ inotify sample code 偵測指定路徑底下的文件變化 (內有範例程式碼)
⭐C++ OpenCV 相關文章整理⭐:
1.【OpenCV】c++ OpenCV – 在 ubuntu 上第一次執行 OpenCV 程式 sample code (內含範例程式碼)
2.【OpenCV】c++ OpenCV - 在圖片上寫上文字 cv::putText sample code (內含範例程式碼)
3.【OpenCV】c++ OpenCV - cv::Rect 矩形用法與相關功能函數 sample code (內含範例程式碼)
4.【OpenCV】c++ OpenCV - OpenCV 中的純量 定義顏色 cv::Scalar(255,255,255) color sample code (內含範例程式碼)
5.【OpenCV】用 C++ 計算 iou 的方法 與網路算法常見錯誤(內附範例程式碼) sample code
⭐C++ Makefile 相關文章整理⭐:
1.【C++ Makefile】- 1 / 嘗試撰寫自己的第一份 Makefile
2.【C++ Makefile】- 2 / 新增自己的變數
3.【C++ Makefile】- 3 / Makefile 常用變數 -「[email protected]」, 「$^」
4.【C++ Makefile】- 4 / Makefile 常用 fake target -「.PHONY」
5.【C++ Makefile】- 5 / Makefile 內建變數 -「[email protected]」, 「$^」, 「$<」, 「$* 」,「$? 」
⭐【喜歡我的文章嗎? 歡迎幫我按讚~ 讓基金會請創作者喝一杯咖啡!
如果喜歡我的文章,請幫我在下方【按五下Like】 (Google, Facebook 免註冊),會由 「LikeCoin」 贊助作者鼓勵繼續創作,讀者們「只需幫忙按讚,完全不用出錢」哦!

likecoin-steps
Howard Weng
Howard Weng

我是 Howard Weng,很多人叫我嗡嗡。這個網站放了我的各種筆記。希望這些筆記也能順便幫助到有需要的人們!如果文章有幫助到你的話,歡迎幫我點讚哦!

文章: 728

1 則留言

★留個言吧!內容有誤或想要補充也歡迎與我討論!