# 【C++】Smart Pointer 智慧指標筆記 ## Smart Pointers 在 `C++11` 中的標準模板庫 ( STL,為了要解決**記憶體洩漏問題 ( Memory Leak )** 的問題,便引進了 Smart Pointer 來更方便管理記憶體,如果要使用 Smart Pointer 就要使用標頭檔案為 `<memory>` > Smart Pointers enable automatic, exception-safe, object lifetime management. > [cppreference](https://en.cppreference.com/w/cpp/memory) :::danger 如果在 `free` 跟 `delete` 前程式碼就因為 Error 而結束,造成記憶體沒有釋放,對於系統穩定性造成一定程度的危害 ::: 嚴格上來說智慧指標是由**模板類別**來進行實作,可以參考 > [unique_ptr.h](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/unique_ptr.h#L271) > [shared_ptr.h](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L534)  👉[C 語言程式記憶體配置概念](/byDxZ7bCRAaCTDUYTjP66w) ## `std::unique_ptr` `unique_ptr` 是一種smart pointer,並且確保 `unique_ptr` 的所指向的物件只會被一個智慧指標進行管理,當超出作用域時變自動進行銷毀 ### Declare 宣告 `unique_ptr` 有以下兩種方法 * 在 `C++11` ,使用 `std::unique_ptr<T> ptr(new T(...));` * 在 `C++14` ,使用 `std::make_unique<T>(Args&& ... args)` ```cpp! class A{ public: A(){std::cout << "A created" << std::endl;} ~A(){ std::cout << "A destroyed" << std::endl;} }; int main() { std::cout << "Example 1" << std::endl; { std::unique_ptr<A> aInst1(new A()); // C++11 std::unique_ptr<A> aInst2 = std::make_unique<A>(); // C++14 } std::cout << "Example 1 out of scope" << std::endl; return 0; } ``` * 以下例子為嘗試將 `unique_ptr` 進行賦值動作,在編譯時期就會出錯! 原因是因為在其模板函數的實作中,已經將賦值的 operator 禁用了 * 如果需要轉移 `unique_ptr` 的 ownership,就需要使用 `std::move`,移動完成後本來的`aInst`就會是空指標 ```cpp std::unique_ptr<A> otherA; std::unique_ptr<A> aInst = std::make_unique<A>(); // C++14 otherA = aInst; // error: use of deleted function otherA = std::move(aInst); // success ``` ## `std::shared_ptr` `shared_ptr` 是一種可以共享同一個資源的智慧指標,內部會記錄這份資源被使用的次數 (Reference Counter) ,只要還有 `shared_ptr` 指向的物件存在時、資源就不會釋放;只有當所有使用這份資源的 shared_ptr 物件都消失的時候,資源才會被自動釋放 * 初始化 Reference Counter設為 1 * 拷貝或附值 Reference Counter會加 1 * Reset Reference Counter設為 0 ### Declare * 在 `C++11` ,使用 `std::shared_ptr<T> ptr(new T(...));` * 在 `C++14` ,使用 `std::make_shared<T>(Args&& ... args)` ```cpp! #include <iostream> #include <memory> #define watch(shPtr) std::cout << "get: " << shPtr.get() << " use_count: " << shPtr.use_count() << std::endl class A { public: A(){std::cout << "A created" << std::endl;} ~A(){ std::cout << "A destroyed" << std::endl;} }; int main() { std::shared_ptr<A> a = std::make_shared<A>(); watch(a); std::shared_ptr<A> otherA = a; watch(a); watch(otherA); return 0; } ``` ## `std::weak_ptr` 搭配 `shared_ptr` 使用的smart pointer,和 `shared_ptr` 的不同點在於 `weak_ptr` 不會影響資源被使用的次數,也就是說的 `weak_ptr` 存在與否不代表資源會不會被釋放掉,而 `weak_ptr` 只是用來檢測記憶體是否正確被釋放  ## Smart Pointer Operator * `a->b` = `(*a).b` : 用於取得智慧指標的屬性或方法 * `(*ptr)` and `ptr.get()` : 取得智慧指標中的**指向物件的原生指標**,回傳 Reference ,不影響智慧指標,使用場景通常為將指向物件只有要被使用,但不改變內部狀態時候傳入使用,避免造成循環引用造成記憶體洩漏 ## Ownership 擁有權 藉由動態配置 ( 例如通過 `new` 或 `std::make_unique` 創建的物件 ) 的物件或函數都會由一個管理者,負責在物件不再需要的時候將物件刪除,而所有權是可以共享的,也就是可以將物件或函數傳遞到下一個管理者,而最後一個管理者要負責將物件刪除,也就是物件或函數創建與刪除,是有可能在不同管理者之間執行的,造成管理記憶體的難處 ## 動態分配物件的傳遞方式 ### 傳遞引用或指標 分配物件的程式碼(例如一個類別)負責管理物件的生命週期,其他程式碼只「借用」該物件,因此我們可以透過引用方式來「借用」該物件,分為 `const Type&` 與 `Type&` 或者 `const Type*` 與 `Type*`,優點就是避免不必要的複製開銷、同時管理權限分明,誰創建誰就負責銷毀,借用者沒有資格去銷毀該借用物件,上面有看到有時候用常數引用或者引用,則是端看借用者是否需要修改該物件狀態,如果有則不能使用常數引用或指標 :::info * 引用無法使用空指標,如果該物件有可能不存在,請用指標 * 請確保借用的程式碼所使用物件期間,該物件都存在,不會突然被銷毀 ::: ### 傳遞副本 基本上小型物件或者配置檔案,都可以用複製的,或者該類別需要長期持有該物件 ## 結論 如果需要動態配置的話,儘可能把所有權保持在配置記憶體的那段程式碼中。如果其他地方也需要存取該物件的話,考慮複製一份物件,或是傳遞物件的指標 (Raw Pointer) 或 reference `&` 方式,不要傳遞所有權。如要真的要轉移所有權 (Ownership) 最好使用 `std::unique_ptr` 明確表達所有權的轉移,至於是否要複製或者傳遞原指標則需要考量多個因素進而影響實作 ## 以 Status Pattern 狀態設計模式 以狀態設計模式為例,我們可以透過 `Context` 管理各個狀態所需要的資源,並藉由原始指標 (Raw pointer) 方式傳入到 `State` 當中,讓各個從 `State` 衍生出的類別,可以使用這些資源,這樣便提供了類似一個工具包的角色,並讓不同狀態去取用,同時也可以**傳遞狀態間的資源或資料轉移** ```cpp! class State { protected: Context* context_ = nullptr; public: void setContext(Context* ctx) { context_ = ctx; } }; ``` ```cpp! class Context { public: Context(); ~Context(); private: std::shared_ptr<Resource> resource_; // Shared resource }; ``` ## 補充知識點 1. 作用域Scope: 程式碼有效的區域,例如 python 的縮排 The scope of a name binding (an association of a name to an entity, such as a variable) is the part of a program where the name binding is valid.
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up