智慧指標用法學習

lypbendlf發表於2024-05-21

轉自:https://blog.csdn.net/cpp_learner/article/details/118912592,chatgpt

1.介紹

智慧指標就是幫管理動態分配的記憶體的,它會幫助我們自動釋放new出來的記憶體,從而避免記憶體洩漏!使用智慧指標可以自動呼叫物件的解構函式。

例子:

class Test {
public:
    Test() { cout << "Test的建構函式..." << endl; }
    ~Test() { cout << "Test的解構函式..." << endl; }

    int getDebug() { return this->debug; }

private:
    int debug = 20;
};

int main() { 
    //Test *test = new Test;
    shared_ptr<Test> test(new Test);

    cout << "test->debug:" << test->getDebug() << endl;
    cout << "(*test).debug:" << (*test).getDebug() << endl;
    return 0;
}
//輸出結果
Test的建構函式...
test->debug:20
(*test).debug:20
Test的解構函式... 

智慧指標可以像普通指標一樣使用-> 和*取內容,是因為 shared_ptr過載了這2個運算子函式:

1.1 get方法

獲取智慧指標託管的指標地址。但一般不這樣用,直接用智慧指標操作即可。

// 定義智慧指標
shared_ptr<Test> test(new Test);

Test *tmp = test.get();        // 獲取指標返回
cout << "tmp->debug:" << tmp->getDebug() << endl;

// 函式原型
_NODISCARD _Ty * get() const noexcept
{    // return wrapped pointer
    return (_Myptr);// 直接返回託管的指標
}

1.2 release方法

取消智慧指標對動態記憶體的託管。

// 定義智慧指標
shared_ptr<Test> test(new Test);

Test *tmp2 = test.release();    // 取消智慧指標對動態記憶體的託管
delete tmp2;    // 之前分配的記憶體需要自己手動釋放

// 函式原型
_Ty * release() noexcept
{    // return wrapped pointer and give up ownership
    _Ty * _Tmp = _Myptr; 
    _Myptr = nullptr;// 將成員指標設定為空
    return (_Tmp);// 將指標返回外界,由外界來控制是否delete
}

1.3 reset方法

重置智慧指標託管的記憶體地址,如果地址不一致,原來的會被析構掉。

// 重置
p.reset() ; 將p重置為空指標,所管理物件引用計數 減1
p.reset(p1); 將p重置為p1(的值),p 管控的物件計數減1,p接管對p1指標的管控
p.reset(p1,d); 將p重置為p1(的值),p 管控的物件計數減1並使用d作為刪除器
p1是一個指標!

// 交換
std::swap(p1,p2); // 交換p1 和p2 管理的物件,原物件的引用計數不變
p1.swap(p2);    // 交換p1 和p2 管理的物件,原物件的引用計數不變

2.shared_ptr 介紹

記錄引用特定記憶體物件的智慧指標數量,當複製或複製時,引用計數加1,當智慧指標析構時,引用計數減1,如果計數為零,代表已經沒有指標指向這塊記憶體,那麼我們就釋放它。

class Person {
public:
    Person(int v) {
        this->no = v;
        cout << "建構函式 \t no = " << this->no << endl;
    }

    ~Person() {
        cout << "解構函式 \t no = " << this->no << endl;
    }

private:
    int no;
};
int main() { 
    shared_ptr<Person> sp1;
    shared_ptr<Person> sp2(new Person(2));

    // 獲取智慧指標管控的共享指標的數量    use_count():引用計數
    cout << "sp1    use_count() = " << sp1.use_count() << endl;
    cout << "sp2    use_count() = " << sp2.use_count() << endl << endl;

    // 共享
    sp1 = sp2;

    cout << "sp1    use_count() = " << sp1.use_count() << endl;
    cout << "sp2    use_count() = " << sp2.use_count() << endl << endl;

    shared_ptr<Person> sp3(sp1); // 共同託管
    cout << "sp1    use_count() = " << sp1.use_count() << endl; // 可以列印引用計數值
    cout << "sp2    use_count() = " << sp2.use_count() << endl;
    cout << "sp2    use_count() = " << sp3.use_count() << endl << endl;

    return 0;
}

// 輸出結果
建構函式         no = 2
sp1     use_count() = 0
sp2     use_count() = 1

sp1     use_count() = 2
sp2     use_count() = 2

sp1     use_count() = 3
sp2     use_count() = 3
sp2     use_count() = 3

解構函式         no = 2

2.1 使用make_shared構建物件

make_shared<型別>(建構函式引數列表);
shared_ptr<int> up3 = make_shared<int>(2); // 多個引數以逗號','隔開,最多接受十個
shared_ptr<string> up4 = make_shared<string>("字串");
shared_ptr<Person> up5 = make_shared<Person>(9); //()內是建構函式的引數

會將物件指標和引用計數指標分配在一起,更高效。

ref_count也需要一塊堆空間,(這裡vptr是什麼?虛擬函式表的指標?為什麼會放到這裡?)

shared_ptr<Foo> x(new Foo);

這種方式需要為 Foo 和 ref_count 各分配一次記憶體,而使用

shared_ptr<Foo> x=make_shared<Foo>();

會一次分配較大的記憶體供 Foo 和 ref_count 物件容身,(存放到一塊地址空間)。

3.注意事項

  • 在呼叫p.release()時會返回該指標,但不會釋放p所指的記憶體,這時返回值就是對這塊記憶體的唯一索引,如果沒有使用這個返回值釋放記憶體或是儲存起來,這塊記憶體就洩漏了。
  • 禁止delete 智慧指標get 函式返回的指標。析構時造成重複釋放
  • 禁止用任何型別智慧指標get 函式返回的指標去初始化另外一個智慧指標。具體解釋:
//有問題的程式碼
std::shared_ptr<int> sp1(new int(10)); // 管理一個動態分配的整數
std::shared_ptr<int> sp4(sp1.get()); // 直接使用裸指標構造

sp1.get() 返回一個指向動態分配的整數的裸指標,但 shared_ptr 並不共享控制塊和引用計數資訊。如果 shared_ptr 透過裸指標構造,兩個 shared_ptr 將獨立管理同一個指標物件。這會導致兩個 shared_ptr 在各自的生命週期結束時嘗試釋放相同的動態分配物件,從而造成雙重釋放問題,導致未定義行為(通常程式崩潰)。正確用法:

auto sp1 = std::make_shared<int>(10);
auto sp4 = sp1; // 正確: sp4 現在共享 sp1 的控制塊和引用計數

透過複製建構函式來共享物件的所有權,確保引用計數正確管理物件的生命週期。

  • 不要定義指向智慧指標的指標,這違背了智慧指標的初衷。

4.和const

4.1 const std::shared_ptr<T>

指標本身是常量,即你不能修改 shared_ptr 物件(例如,不能讓它指向另一個物件),但透過這個指標可以修改它指向的物件。

    const shared_ptr<string> up4 = make_shared<string>("mystr");
    // 報錯: error: no match for ‘operator=’ (operand types are ‘const std::shared_ptr<std::__cxx11::basic_string<char> >’ and ‘std::shared_ptr<std::__cxx11::basic_string<char> >’)
    up4 = make_shared<string>("mystr2");// 該行程式碼會報錯,不能修改 `up4` 本身
    *up4 = "mmmm";
    cout<<*up4;// 輸出mmmm,可以修改指向的內容

4.2 std::shared_ptr<const T>

指指標本身是可變的(即可以修改指標使其指向另一個物件),但不能修改它指向的物件,即所指物件是常量。當你希望透過 shared_ptr 提供只讀訪問許可權時,可以使用 std::shared_ptr<const T>。

    shared_ptr<const string> up4 = make_shared<string>("mystr");
    up4 = make_shared<string>("mystr2");// 可以更改指向新的物件
    //error: assing ‘const std::__cxx11::basic_string<char>’ as ‘this’ argument discards qualifiers
    *up4 = "mmmm";//報錯,不能修改
    cout<<*up4;

相關文章