Pin-Jiun / Programming-Language-CPP

It's the note/experience about C++, and I have the basic ability of C.
0 stars 0 forks source link

8.1-Smart Pointer #10

Open Pin-Jiun opened 1 year ago

Pin-Jiun commented 1 year ago

在 C/C++ 中,最令人頭痛的事情是:「管理記憶體」-必須自己手動管理物件的生命週期 然而OOP中容易發生以下幾個問題:

這些種種都會考驗到程式設計師的能力,更何況程式碼是由多人維護的,可能程式碼很長,指標指的 object 生命週期很長,但是在途中被 delete 了;又或者是兩個不同的 pointer 指向了同一塊 object ,那麼要由誰來 delete 呢(誰才擁有 ownership),如果誤刪了則可能導致程式崩潰或可能不會(undefinied behavior)

class DataGenerator
{
public:
  int* GetData();
};

DataGenerator DataGen;
int* A = DataGen.GetData();
delete A; // Should do this?

Smart Pointer

為了解決上述問題,C++ 就在 STL 裡面,引進了「Smart Point」的概念 在 C++11 中加入了 unique_ptr, shared_ptr 以及 weak_ptr 古早年代曾有 auto_ptr 嘗試解決這個問題,但不是很成功(在 C++98 被加入 C++17 時移除)

這些 smart pointer 都是 template class 的形式,所以適用範圍很廣泛 他們都是被定義在 這個 header 檔裡、在 std 這個 namespace 下,如果要使用的話,要記得 include 這個 header 檔

unique_ptr

確保一份資源(被配置出來的記憶體空間)只會被一個 unique_ptr 物件管理的 smart pointer 當 unique_ptr 物件消失時,就會自動釋放資源。

一般而言要創立一個指向6這個整數的pointer a如下

int* a = new int(6);  // allocate memory
int b = *a;           // dereference
delete a;             // release resource

使用smart pointer則是unique_ptr<所指向的datatype> pointer_name = make_unique<datatype>() ;

#include <iostream>
#include <memory>
using namespace std;

int main()
{
unique_ptr<int> a = make_unique<int>(6) ;
int b = *a;
cout << b;
//a會自動被free
}

或是

#include <iostream>
#include <memory>
using namespace std;

int main()
{
unique_ptr<int> a = make_unique<int>() ;
*a=6;
int b = *a;
cout << b;
//a會自動被free
}

而由於 unique_ptr 需要確保一份資源只被一個 unique_ptr 擁有,所以他有不可複製的特性,所以像下面的程式碼,是會無法編譯的

unique_ptr<int> a = make_unique<int>(6) ;
unique_ptr<int> b = a;  // compile error!

不過,如果有需要的話,也可以透過 STL 的 std::move() 這個函式,把資源的所有權轉移給別的 unique_ptr 物件,其用法如下:

unique_ptr<int> a = make_unique<int>(6) ;
unique_ptr<int> b = move( a );

轉移所有權後,本來的 unique_ptr 物件(這邊是 a)就不再有這份資源的所有權、也無法再透過它來存取這份資源了

construct and destruct證明其scope

#include <iostream>
#include <memory>
using namespace std;

class MyClass{
public:
    MyClass(){
        cout << "Constructor invoked\n";
    }

    ~MyClass(){
        cout << "destructor invoked\n";
    }

};

int main()
{
    {
        unique_ptr<MyClass> test = make_unique<MyClass>() ;
    }
}

shared_ptr

可以有多個 shared_ptr 共用一份資源的 smart pointer,內部會記錄這份資源被使用的次數(reference counter),只要還有 shared_ptr 物件的存在、資源就不會釋放;只有當所有使用這份資源的 shared_ptr 物件都消失的時候,資源才會被自動釋放。

#include <iostream>
#include <memory>
using namespace std;

class MyClass{
public:
    MyClass(){
        cout << "Constructor invoked\n";
    }

    ~MyClass(){
        cout << "destructor invoked\n";
    }

};

int main()
{
    shared_ptr<MyClass> shPtr1 = make_unique<MyClass>() ;
    cout << "Shared count: " << shPtr1.use_count() << endl;
    shared_ptr<MyClass> shPtr2 = shPtr1;
    cout << "Shared count: " << shPtr1.use_count() << endl;

}

output

Constructor invoked
Shared count: 1
Shared count: 2
destructor invoked

和nuique_ptr一樣,離開其scope會自動摧毀

#include <iostream>
#include <memory>
using namespace std;

class MyClass{
public:
    MyClass(){
        cout << "Constructor invoked\n";
    }

    ~MyClass(){
        cout << "destructor invoked\n";
    }

};

int main()
{
    shared_ptr<MyClass> shPtr1 = make_unique<MyClass>() ;
    cout << "Shared count: " << shPtr1.use_count() << endl;
    {
        shared_ptr<MyClass> shPtr2 = shPtr1;
        cout << "Shared count: " << shPtr1.use_count() << endl;
    }
    cout << "Shared count: " << shPtr1.use_count() << endl;

}

output

Constructor invoked
Shared count: 1
Shared count: 2
Shared count: 1
destructor invoked

weak_ptr

搭配 shared_ptr 使用的 smart pointer,weak_ptr 並不會增加 shared_ptr 的 reference count ,亦不會搶走所有權 (Ownership)

int main(){
shared_ptr<int> shared = make_unique<int>(20);
weak_ptr<int> weak;

cout << "1. weak " << (weak.expired()?"is":"is not") << " expired\n";
// 1. weak is expired
weak = shared;

cout << "2. weak " << (weak.expired()?"is":"is not") << " expired\n";
// 2. weak is not expired
}

結論

unique_ptr 單獨擁有(Ownership)一個資源,如果要給別人要用 std::move() shared_ptr 在需要多個人共同擁有一個資源時使用 weak_ptr 在不想要給擁有權,但又想要它看的到並摸得到資源時

參考資料 http://blog.roy4801.tw/2020/06/04/c++/smart_pointer/ https://kheresy.wordpress.com/2012/03/03/c11_smartpointer_p1/