From b878b74b7c858a632e53a02e7e920e9ddedcb52a Mon Sep 17 00:00:00 2001 From: Awin Huang Date: Mon, 6 Jun 2022 11:47:32 +0800 Subject: [PATCH] =?UTF-8?q?Add=20"[C++17=20=E7=AD=86=E8=A8=98]=20=E6=99=BA?= =?UTF-8?q?=E6=85=A7=E6=8C=87=E6=A8=99=EF=BC=9Aunique=5Fptr=20&=20shared?= =?UTF-8?q?=5Fptr"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../index.md | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 content/posts/2019/[C++17筆記] 智慧指標:unique_ptr & shared_ptr/index.md diff --git a/content/posts/2019/[C++17筆記] 智慧指標:unique_ptr & shared_ptr/index.md b/content/posts/2019/[C++17筆記] 智慧指標:unique_ptr & shared_ptr/index.md new file mode 100644 index 0000000..a820abb --- /dev/null +++ b/content/posts/2019/[C++17筆記] 智慧指標:unique_ptr & shared_ptr/index.md @@ -0,0 +1,123 @@ +--- +slug: "[C++17 筆記] 智慧指標:unique_ptr & shared_ptr" +title: "[C++17 筆記] 智慧指標:unique_ptr & shared_ptr" +description: +toc: true +authors: + - awin +tags: + - c++17 + - memory +categories: + - Programming +series: + - C++17 筆記 +date: 2019-12-01T00:00:00 +lastmod: 2019-12-01T00:00:00 +featuredVideo: +featuredImage: +draft: false +enableComment: true +--- + +[`unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr)與[`shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr)都是智慧指標,箱對於原本的raw pointer,智慧指標使用起來更方便,也不用擔心delete的問題。 + + + +## unique_ptr +`unique_ptr` 的特點是,它保證在一個時間內,只會有一個指標的擁有者,也就是這個指標不能被複製跟移動,當 `unique_ptr` 離開它的scope時候,它所擁有的指標也隨之被delete。這讓你不用擔心memory leak的問題。 +假設我們有一個class叫 `BigBuffer` ,原本分配記憶體的方法: +```cpp +BigBuffer* bigBuf = new BigBuffer(bufferSize); +// Use buffer here +delete bigBuf; +``` + +用 `unique_ptr`: +```cpp +auto bigBuf = std::make_unique(bufferSize); +// Use buffer here +// bigBuf will be released when exiting scope +``` + +我們統一用[`std::make_unique<>`](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique)這個template function來建立 `unique_ptr` ,角括號 `<>` 裡面要帶入你要建立的型別,後面的括號 `()` 就是型別的constructor,使用起來跟 `new` 是一樣的。 +因為 `std::make_unique<>` 裡面已經有表明型別了,所以變數就用 `auto` 就可以了,不用再寫一次型別。 + +一旦 `unique_ptr` 建立之後,使用起來就跟一般指標沒有兩樣,都是用 `->` 來操作:`bigBuf->setXXX()` or `bigBuf->getXXX()`。 +但是別忘記 `unique_ptr` 本身還是一個local variable,所以我們可以用 `.` 來操作 `unique_ptr` ,例如我們可以用 `.reset()` 重新配一個指標: +```cpp +bigBuf.reset(std::make_unique(bufferSizeLarger)); +``` +這時候舊指標會自動delete,然後指向新的指標(如果記憶體分配有成功的話),或者指向 `nullptr` (記憶體分配失敗)。 +如果單純想要釋放指標,那就單純的呼叫 `reset()` 就好。 +```cpp +bigBuf.reset(); // Now I'm nullptr +``` + +如果要分配陣列的話: +```cpp +auto intArray = std::make_unique(1024); +``` + +使用方式也是一樣的: +```cpp +intArray[5] = 555; +``` + +不過對於陣列的操作更建議使用 `std::array` 。 + +如果有什麼特殊原因讓你決定不再讓 `unique_ptr` 來幫你管理指標,可以用 `release()` 來讓出指標: +```cpp +auto intArray = std::make_unique(1024); +int* intArrayRaw = intArray.release(); // Now I don't care anymore +``` +但是這時候呼叫 `delete[]` (或 `delete` )的責任又回到你身上了。所以千萬不要把 `release()` 跟 `reset()` 搞混了。 + +`unique_ptr` 不能被複製跟移動,所以下列的寫法都編不過: +```cpp +auto ptr1 = std::make_unique(5); +std::unique_ptr ptr2(ptr1); // Error +std::unique_ptr ptr2 = ptr1; // Error +``` +在Visual Studio 2017上,錯誤訊息是這樣:`error C2280: 'std::unique_ptr>::unique_ptr(const std::unique_ptr> &)': attempting to reference a deleted function`。 +其實就是`unique_ptr`的copy constructor跟assignment operator都被標記為delete了。 + + +## shared_ptr +建立一個 `shared_ptr` 是使用[`std::make_shared()`](https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared): +```cpp +auto myBuf = std::make_shared(bufferSize); +``` + +但是 `shared_ptr` 可以被複製與移動,這是跟 `unique_ptr` 的差別: +```cpp +auto myBuf = std::make_shared(bufferSize); + +std::shared_ptr bufCopy = myBuf; +``` + +現在 bufCopy 跟 myBuf 都指向同一個指標,他們都可以操作這個指標: +```cpp +myBuf->setZero(startAddr, endAddr); +bufCopy->setOne(startAddr, endAddr); +``` + +`shared_ptr` 內部有一個參考記數(reference count)來紀錄它所擁有的指標已經分享給幾個變數了,只要有變數離開了他的scope,參考記數就會減少,反之,要是像上面那樣有人複製指標,參考記數就會增加,參考記數歸0的時候,指標就會被釋放。 + +有了 `shared_ptr` 我們就不必擔心 delete 的責任問題: +```cpp +std::shared_ptr getBuffer(int32_t bufferSize) { + return std::make_shared(bufferSize); +} + +int main() { + auto myBuf = getBuffer(1024); // new(malloc) memory + // use myBuf + + return 0; +} // myBuf delete memory here +``` + +`shared_ptr` 有一個問題是可以會「循環參考」(cyclic references),也就是 share_ptr A 指向另一個 share_ptr B ,然後 share_ptr B 又指向 share_ptr A,這造成參考記數(reference count)不會減少而永遠無法釋出指標。這個是需要注意的。 + +但是 `shared_ptr` 還是讓記憶體的管理問題大大減少,應該用 `shared_ptr` 來代替 `new` & `delete` 。