Try   HackMD

【C++】Smart Pointer 智慧指標筆記

Smart Pointers

C++11 中的標準模板庫(STL,為了要解決記憶體洩漏問題(Memory Leak) 的問題,便引進了Smart Pointer來更方便管理記憶體,而標頭檔案為 <memory>

Smart pointers enable automatic, exception-safe, object lifetime management.
cppreference

如果在 freedelete 前程式碼就爆開了,當記憶體空間沒有釋放到一定程度就會爆開

嚴格上來說智慧指標是由模板類別來進行實作,可以參考

unique_ptr.h
shared_ptr.h

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

👉C 語言程式記憶體配置概念

std::unique_ptr

unique_ptr 是一種smart pointer,並且確保 unique_ptr 的所指向的物件只會被一個智慧指標進行管理,當超出作用域時變自動進行銷毀

Declare

  • C++11 ,使用 std::unique_ptr<T> ptr(new T(...));
  • C++14 ,使用 std::make_unique<T>(Args&& ... args)
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就會是空指標
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)
#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 只是用來檢測記憶體是否正確被釋放

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Smart Pointer Operator

  • a->b = (*a).b
    用於取得智慧指標的屬性或方法
  • (*ptr) and ptr.get()
    取得智慧指標中的指向物件的原生指標,回傳 reference ,不影響智慧指標,使用場景通常為將指向物件只有要被使用,但不改變內部狀態時候傳入使用,避免造成循環引用造成記憶體洩漏

Ownership 擁有權

經由動態配置的物件或函數都會一個管理者,負責在物件不再需要的時候將物件刪除,而所有權是可以共享的,也就是可以將物件或函數傳遞到下一個管理者,而最後一個管理者要負責將物件刪除,也就是物件或函數創建與刪除,是有可能在不同管理者之間執行的,造成管理記憶體的難處

結論

如果需要動態配置的話,儘可能把所有權保持在配置記憶體的那段程式碼中。如果其他地方也需要存取該物件的話,考慮複製一份物件,或是傳遞物件的指標 (Raw Pointer) 或 reference & 方式,不要傳遞所有權。如要真的要轉移所有權 (Ownership) 最好使用 std::unique_ptr 明確表達所有權的轉移

以狀態設計模式為例

以狀態設計模式為例,我們可以透過 Context 管理各個狀態所需要的資源,並藉由原始指標 (raw pointer) 方式傳入到 State 當中,讓各個從 State 衍生出的類別,可以使用這些資源,這樣便提供了類似一個工具包的角色,並讓不同人去取用,同時也可以傳遞狀態間的資源或資料轉移

class State {
protected:
    Context* context_ = nullptr;
public:
    void setContext(Context* ctx) { context_ = ctx; }
};
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.