Overview
unique_ptr
- move construct only
shared_ptr
- reference counting
weak_ptr
- weak references
boost:scoped_ptr
- only in one scope
Smart Pointer ?
在 C/C++ 中,最令人頭痛的事情是:「管理記憶體」,必須自己手動管理物件的生命週期,如果忘記 delete
或是拋出了 exception ,會造成記憶體洩漏(Memory Leak);或是 delete
後,pointer 並沒有清空,後面的 code 又再度使用而導致 Use-After-Free 發生;或是一個 pointer 被重複 delete 了一次以上 (double free)。
1 | void foo() |
這些種種都會考驗到程式設計師的能力,更何況程式碼是由多人維護的,可能程式碼很長,指標指的 object 生命週期很長,但是在途中被 delete 了;又或者是兩個不同的 pointer 指向了同一塊 object ,那麼要由誰來 delete 呢(誰才擁有 ownership),如果誤刪了則可能導致程式崩潰或可能不會(undefinied behavior)。
1 | Foo* genFoo() |
於是有人便想到了使用 class
來將 pointer 封裝,古早年代曾有 auto_ptr
嘗試解決這個問題,但不是很成功(在 c++98
被加入 c++17
時移除),在 C++11 中加入了 unique_ptr
, shared_ptr
以及 weak_ptr
。
1 | // 維護 ptr 是你的責任! |
上面的例子便可以改寫成:
1 | unique_ptr<Foo> genFoo() |
如此一來便不用擔心資源不被釋放了,因為 unique_ptr
出了 scope 便會釋放(RAII)
unique_ptr<T>
Use
std::unique_ptr
for exclusive-ownership resource management- unique ownership
- 一個資源只會被一個 object 所擁有
- unique ownership
unique_ptr<T>
不能複製(operator=
)、不能 copy-construct只能被 move-construct
- 代表所有權(Ownership)的轉移
1 | unique_ptr<int> a(new int{10}); |
標準庫
1 | unique_ptr<int> a = make_unique<int>(1); |
1 | b is good |
unique_ptr<T[]>
unique_ptr 可以放 array types ,並且會被正確釋放 (呼叫delete []
)
1 | struct Foo { |
- 甚至可以自訂
deleter
(如何刪除 pointer)
1 | unique_ptr<Foo, std::function<void(Foo*)>> p(new Foo, [](Foo *p) { |
- 使用 C++11 alias template,可以不用指定 delete 的 type
1 | struct Widget {}; |
使用
- 用起來就跟 raw pointer 沒兩樣
1 | ue_ptr<int> a = make_unique<int>(); |
void reset (pointer p = pointer())
重設
1 | unique_ptr<int> a = make_unique<int>(123); |
pointer release()
釋放所有權- 釋放
unique_ptr
所維護的指標的所有權(Ownership)- 回傳 pointer 並將
unique_ptr
內部的ptr = nullptr
- 回傳 pointer 並將
- Example
- 釋放
1 | unique_ptr<int> a = make_unique<int>(); |
pointer get()
獲取unique_ptr
底下的 raw pointer
1 | unique_ptr<int> a = make_unique<int>(10); |
一些坑
- unique-ownership
unique_ptr
是獨占資源的,如果用同個 raw pointer 來初始化多個unique_ptr
會被 delete 數次- 以下是錯誤的範例
1 | struct Foo {}; |
Exception 安全
用 Raw pointer 創建
unique_ptr
不保證 exception 安全1
func(unique_ptr<Foo>(new Foo), func_throw_exception());
- C++ 標準並沒有規定對參數的 evaluate 之順序
- 所以可能出現這樣的順序:
new Foo
func_throw_exception()
unique_ptr<Foo>(...)
- 在
func_throw_exception()
時會拋出 exception,導致無法建構unique_ptr
,造成new Foo
無法回收,導致記憶體洩漏(Memory Leak)
- 所以可能出現這樣的順序:
- C++ 標準並沒有規定對參數的 evaluate 之順序
使用
make_unique<T>()
則可以解決這個問題1
func(make_unique<Foo>(), func_throw_exception());
到了 c++14 才有
make_unique<T>()
這個 function,並沒有在以前的標準,但是自己實現並不複雜
1 | template<typename T, typename... Args> |
shared_ptr<T>
跟 unique_ptr
不同的是,shared_ptr
可以讓同個資源給多個 shared_ptr
「共用」,所以 shared_ptr
可以複製。
1 | shared_ptr<int> a = make_shared<int>(10); |
shared_ptr
內部實作 Reference Count ,每當有一個 shared_ptr
建立並指向同個資源時,Reference Count 變加一,當一個 shared_ptr
被 destruct 時,會把 Reference Count 減一。當最後一個指向資源的 shared_ptr
被 destruct 時,則會釋放資源。
比喻:有一個房間有一盞燈(資源),房間裡有很多人(共享),約定好最後一個出去的關燈(釋放資源)
指向同個 object 之所有的 shared_ptr
共用一個 Control Block ,上頭有 Reference Count 以及其他 shared_ptr
會用到的東西
使用
shared_ptr
的用法跟unique_ptr
差不多use_count()
回傳shared_ptr
的 reference count1
2
3shared_ptr<int> a = make_shared<int>();
shared_ptr<int> b = a;
printf("%d\n", b.use_count()); // 2unique()
是否唯一1
2
3
4
5
6shared_ptr<int> a = make_shared<int>();
{
shared_ptr<int> b = a;
printf("%d\n", b.unique()); // 0
}
printf("%d\n", a.unique()); // 1shared_ptr
有提供轉型指標(cast)static_pointer_cast<T>(sp)
- 相當於
static_cast<T*>(sp.get())
- 相當於
dynamic_pointer_cast<T>(sp)
- 相當於
dynamic_cast<T*>(sp.get())
- 相當於
const_pointer_cast<T>(sp)
- 相當於
const_cast<T*>(sp.get())
- 相當於
- Example
一些坑
但是事情並沒有那麼美好,shared_ptr
還是會有些坑
用同個 raw pointer 重複初始化
shared_ptr
- 會導致重複釋放資源
- Example
1
2
3
4
5Foo *f = new Foo();
shared_ptr<Foo> a(f);
{
shared_ptr<Foo> b(f);
}- 結論:用
make_shared<>()
就好
unique_ptr
可以轉成shared_ptr
,但是反過來不行shared_ptr
原生不支援陣列型態- 沒有
operator[]
- 沒有特化的 deltetr,必須自己給
- 沒有
循環參考 (Circular Reference)
1
2
3
4
5
6
7
8
9
10struct Foo{
/* ... */
shared_ptr<Foo> next;
};
shared_ptr<Foo> a = make_shared<Foo>(1);
shared_ptr<Foo> b = make_shared<Foo>(2);
a->next = b;
b->next = a;- 猜猜上面的 code 到最後有誰會被釋放
- 答案是 0 個
- 因為循環參考(Circular Reference)的關係
- 猜猜上面的 code 到最後有誰會被釋放
weak_ptr<T>
weak_ptr
並不會增加 shared_ptr
的 reference count ,亦不會搶走所有權 (Ownership)
用於解決循環參考(Circular Reference)的問題,跟 shared_ptr
搭配使用
將上面的例子改寫成:
1 | struct Foo{ |
最後則會正常釋放
使用
weak_ptr
並不能直接存取(沒有operator->
),如果要存取的話,必須使用.lock()
轉成shared_ptr
1 | shared_ptr<int> a = make_shared<int>(10); |
.expired()
查看weak_ptr
使否可用
1 | std::shared_ptr<int> shared (new int(10)); |
結論
unique_ptr
單獨擁有(Ownership)一個資源,如果要給別人要用std::move()
shared_ptr
在需要多個人共同擁有一個資源時使用weak_ptr
在不想要給擁有權,但又想要它看的到並摸得到資源時
參考
Learncpp
https://www.learncpp.com/
Why is auto_ptr being deprecated?
https://stackoverflow.com/questions/3697686/why-is-auto-ptr-being-deprecated
CppCon 2019: Arthur O’Dwyer “Back to Basics: Smart Pointers”
https://www.youtube.com/watch?v=xGDLkt-jBJ4
深入 C++ 的 unique_ptr
http://senlinzhan.github.io/2015/04/20/%E8%B0%88%E8%B0%88C-%E7%9A%84%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/
How to implement make_unique function in C++11?
https://stackoverflow.com/questions/17902405/how-to-implement-make-unique-function-in-c11
山姆大叔談 C++:從歷史談起,再給個定義—Modern C++ 解惑
https://ithelp.ithome.com.tw/articles/10213866
https://ithelp.ithome.com.tw/articles/10214337
https://kheresy.wordpress.com/2012/03/05/c11_smartpointer_p2/
https://blog.jaycetyle.com/2019/11/passing-smart-pointer/
如果你覺得這篇文章很棒,請你不吝點讚 (゚∀゚)