零、總覽
|類型|策略|能否用於數組|備註|
|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;
}
輸出:
再舉一個經常遇到的情況:容器中存儲指針,但是隻釋放了容器而沒有釋放容器內的指針。
如果不使用智能指針的話,比如說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在生命週期結束後會再次釋放對應內存導致崩潰