c++智能指針學習和遇到的問題

零、總覽

|類型|策略|能否用於數組|備註|
|auto_ptr| 所有權模型| 否| -
unique_ptr 所有權模型 能 策略比auto_ptr更嚴格
shared_ptr 引用計數模型 否 -

一、auto_ptr的使用以及問題

auto_ptr是一個控制權轉換的指針,兩個auto_ptr賦值賦值的話,會釋放賦值運算符右側變量的所有權。c++11 之後不推薦使用,問題較多。
比如下面的代碼:

void testAuto_ptr()
{
	int *p = new int(10);
	auto_ptr<int> ap(p);
	auto_ptr<int> ap2 = ap;
	cout << *ap << endl;
}

運行之後會直接崩潰,因爲在auto_ptr ap2 = ap這句話結束之後,ap這個指針已經沒有p所指向的內存的權限了。

二、unique_ptr和auto_ptr對比

unique_ptr也是所有權模型,但是他比auto_ptr要嚴格得多,不允許使用拷貝構造和賦值運算符,所以在一定程度生能避免auto_ptr的問題
和第一部分相似的代碼,如果改成unique_ptr,會直接報錯
在這裏插入圖片描述

三、unique_ptr的問題

雖然不能直接進行拷貝構造和賦值運算,但是如果兩個unique_ptr都指向一個內存塊的話,釋放會釋放兩次,程序結束會直接崩潰,比如下面的代碼:

void testUnique_ptr()
{
	int *p = new int(10);
	unique_ptr<int> up(p);
	unique_ptr<int> up2(p);
	cout << *up << endl;
}

運行結果:
在這裏插入圖片描述
原因是雖然unique_ptr都獨享p指針所指向的內存區域,但是函數結束自動釋放的時候,會重複釋放,這種情況下unique_ptr就有問題了。

四、shared_ptr的好處

shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。

  • 初始化。智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr p4 = new int(1);的寫法是錯誤的
  • 拷貝和賦值。拷貝使得對象的引用計數增加1,賦值使得原對象引用計數減1,當計數爲0時,自動釋放內存。後來指向的對象引用計數加1,指向後來的對象。
  • get函數獲取原始指針
  • 注意不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
  • 注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會導致堆內存無法正確釋放,導致內存泄漏。循環引用在weak_ptr中介紹。
void testShared_ptr()
{
	shared_ptr<int> up = make_shared<int>(10);
	{
		shared_ptr<int> up2 = make_shared<int>(20);
		cout << up.use_count() << endl;
		cout << up2.use_count() << endl;
		up2 = up;
		cout << up.use_count() << endl; //輸出2,兩個shared_ptr指針指向同一塊內存區域
		cout << up2.use_count() << endl; //輸出2,兩個shared_ptr指針指向同一塊內存區域
	}
	cout << up.use_count() << endl; // 輸出1,因爲up2生命週期結束後釋放掉了,這時候up的引用計數-1
	cout << *up << endl;

}

輸出:![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200419185040267.png

再舉一個經常遇到的情況:容器中存儲指針,但是隻釋放了容器而沒有釋放容器內的指針。
如果不使用智能指針的話,比如說vector中存儲的是指針類型:

#include <stdio.h>
#include <vector>
#include <iostream>
#include <memory>
using namespace std;
class myClass
{
    public:
    int my_a;
    myClass(int a):my_a(a){}
    ~myClass() {
        cout << "~myClass" << endl;
    }
};

void test()
{
    myClass *p = new myClass(10);
    vector<myClass*>vtp;
    vtp.push_back(p);
    myClass *q = p;
    cout << "p:" << p << " q:" << q << endl;
    cout << "*p:" << p->my_a << " *q:" << (*q).my_a << endl;
    // for (auto it = vtp.begin(); it != vtp.end(); ++it) {
    //     delete *it;
    // }
    // cout << "*p:" << p->my_a << " *q:" << (*q).my_a << endl;
}

void test_shareptr()
{
    shared_ptr<myClass> p = make_shared<myClass>(10);
    vector<shared_ptr<myClass> >vtp;
    vtp.push_back(p);
    shared_ptr<myClass> q = p;
    cout << "p:" << p << " q:" << q << endl;
    cout << "*p:" << p->my_a << " *q:" << (*q).my_a << endl;
}

int main()
{
    test();
    cout << "---------" << endl;
    test_shareptr();
    return 0;
}

輸出結果:
在這裏插入圖片描述
可以發現普通的指針在函數結束之後也沒有釋放內存,但是智能指針自己就釋放了,所以推薦都使用智能指針

五、指針智能使用注意事項

智能指針不要和普通指針同時使用,當代碼量較大的時候,有可能有的地方會使用普通指針並下意識的使用完delete了,這種情況下shared_ptr在生命週期結束後會再次釋放對應內存導致崩潰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章