[C++ 筆記] 智慧指標:unique_ptr & shared_ptr
unique_ptr
與shared_ptr
都是智慧指標,箱對於原本的raw pointer,智慧指標使用起來更方便,也不用擔心delete的問題。
unique_ptr
unique_ptr
的特點是,它保證在一個時間內,只會有一個指標的擁有者,也就是這個指標不能被複製跟移動,當 unique_ptr
離開它的scope時候,它所擁有的指標也隨之被delete。這讓你不用擔心memory leak的問題。
假設我們有一個class叫 BigBuffer
,原本分配記憶體的方法:
BigBuffer* bigBuf = new BigBuffer(bufferSize);
// Use buffer here
delete bigBuf;
用 unique_ptr
:
auto bigBuf = std::make_unique<BigBuffer>(bufferSize);
// Use buffer here
// bigBuf will be released when exiting scope
我們統一用std::make_unique<>
這個template function來建立 unique_ptr
,角括號 <>
裡面要帶入你要建立的型別,後面的括號 ()
就是型別的constructor,使用起來跟 new
是一樣的。
因為 std::make_unique<>
裡面已經有表明型別了,所以變數就用 auto
就可以了,不用再寫一次型別。
一旦 unique_ptr
建立之後,使用起來就跟一般指標沒有兩樣,都是用 ->
來操作,例如:
bigBuf->setXXX();
bigBuf->getXXX();
但是別忘記 unique_ptr
本身還是一個local variable,所以我們可以用 .
來操作 unique_ptr
,例如我們可以用 .reset()
重新配一個指標:
BigBuffer* pBuffer = new BigBuffer();
bigBuf.reset(pBuffer);
這時候舊指標會自動delete,如果記憶體分配有成功的話,bigBuf會接管剛剛new出來的指標,或者變成 nullptr
(記憶體分配失敗)。
如果單純想要釋放指標,那就單純的呼叫 reset()
就好。
bigBuf.reset(); // Now I'm nullptr
如果要分配陣列的話:
auto intArray = std::make_unique<int[]>(1024);
使用方式也是一樣的:
intArray[5] = 555;
不過對於陣列的操作更建議使用 std::array
。
如果有什麼特殊原因讓你決定不再讓 unique_ptr
來幫你管理指標,可以用 release()
來讓出指標:
auto intArray = std::make_unique<int[]>(1024);
int* intArrayRaw = intArray.release(); // Now I don't care anymore
但是這時候呼叫 delete[]
(或 delete
)的責任又回到你身上了。所以千萬不要把 release()
跟 reset()
搞混了。
unique_ptr
不能被複製跟移動,所以下列的寫法都編不過:
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()
來轉移:
auto ptr1 = std::make_unique<int>(5);
// do something
auto anotherPtr = std::move(ptr1);
ptr1
原本所管理的指標會轉移給 anotherPtr``,ptr1
會變成 nullptr。
shared_ptr
建立一個 shared_ptr
是使用std::make_shared()
:
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
但是 shared_ptr
可以被複製與移動,這是跟 unique_ptr
的差別:
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
std::shared_ptr<BigBuffer> bufCopy = myBuf;
現在 bufCopy 跟 myBuf 都指向同一個指標,他們都可以操作這個指標:
myBuf->setZero(startAddr, endAddr);
bufCopy->setOne(startAddr, endAddr);
shared_ptr
內部有一個參考記數(reference count)來紀錄它所擁有的指標已經分享給幾個變數了,只要有變數離開了他的scope,參考記數就會減少,反之,要是像上面那樣有人複製指標,參考記數就會增加,參考記數歸0的時候,指標就會被釋放。
有了 shared_ptr
我們就不必擔心 delete 的責任問題:
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
。