Files
Obsidian-Main/20.02. CPP/智慧指標.md
Awin Huang afeb207be2 vault backup: 2025-08-20 14:35:48
Affected files:
Too many files to list
2025-08-20 14:35:48 +08:00

111 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[`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<BigBuffer>(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
BigBuffer* pBuffer = new BigBuffer();
bigBuf.reset(pBuffer);
```
這時候舊指標會自動delete如果記憶體分配有成功的話bigBuf會接管剛剛new出來的指標或者變成 `nullptr` (記憶體分配失敗)。
如果單純想要釋放指標,那就單純的呼叫 `reset()` 就好。
```cpp
bigBuf.reset(); // Now I'm nullptr
```
如果要分配陣列的話:
```cpp
auto intArray = std::make_unique<int[]>(1024);
```
使用方式也是一樣的:
```cpp
intArray[5] = 555;
```
不過對於陣列的操作更建議使用 `std::array`
如果有什麼特殊原因讓你決定不再讓 `unique_ptr` 來幫你管理指標,可以用 `release()` 來讓出指標:
```cpp
auto intArray = std::make_unique<int[]>(1024);
int* intArrayRaw = intArray.release(); // Now I don't care anymore
```
但是這時候呼叫 `delete[]` (或 `delete` )的責任又回到你身上了。所以千萬不要把 `release()``reset()` 搞混了。
`unique_ptr` 不能被複製跟移動,所以下列的寫法都編不過:
```cpp
auto ptr1 = std::make_unique<int>(5);
std::unique_ptr<int> ptr2(ptr1); // Error
std::unique_ptr<int> ptr2 = ptr1; // Error
```
在Visual Studio 2017上錯誤訊息是這樣`error C2280: 'std::unique_ptr<int,std::default_delete<int>>::unique_ptr(const std::unique_ptr<int,std::default_delete<int>> &)': attempting to reference a deleted function`
其實就是`unique_ptr`的copy constructor跟assignment operator都被標記為delete了。
### Move a unique_ptr
如果一定要把 unique_ptr 指定給別人可以嗎?可以的,用 `std::move()` 來轉移:
```cpp
auto ptr1 = std::make_unique<int>(5);
// do something
auto anotherPtr = std::move(ptr1);
```
ptr1原本所管理的指標會轉移給 anotherPtrptr1 會變成 nullptr。
## shared_ptr
建立一個 `shared_ptr` 是使用[`std::make_shared()`](https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared)
```cpp
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
```
但是 `shared_ptr` 可以被複製與移動,這是跟 `unique_ptr` 的差別:
```cpp
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
std::shared_ptr<BigBuffer> 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<BigBuffer> getBuffer(int32_t bufferSize) {
return std::make_shared<BigBuffer>(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`