前言
此文章目前尚未整理完成,如需學習完整內容可參考隨附的 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++
- DAY 1:何謂「Modern C++」?從歷史談起,再給個定義——後篇
- int *array = new int[n]; what is this function actually doing?
- How to create a dynamic array of integers
- C語言中的 int** 是什麼?
- delete vs delete[] operators in C++
- What is a dangling pointer?
- 【轉載】C++ free與delete區別
- std::unique_ptr::operator[]
- C++11 unique_ptr智能指针详解
- DAY 2:指標是功能還是臭蟲?兼談 Smart Pointer(拜託不要翻成「聰明指標」)的必要性
- 【轉載】C++ free與delete區別
- effective c++ 筆記
[…] […]