一、循環引用問題
shared_ptr
出現循環引用的根本原因在於引用計數失效,導致其管理的空間無法釋放。
舉個簡單的例子,class A
定義了一個指向自身類類型的智能指針m_spA
。
class A
{
public:
A(int i);
~A() { cout << "A delete!" << endl; }
shared_ptr<A> m_spA;
private:
int m_i;
};
A::A(int i) :m_i(i)
{
cout << "A create!" << endl;
}
假如我們這麼使用class A
:
int main()
{
shared_ptr<A> spA=make_shared<A>(1); //A(1) 引用計數0->1
spA->m_spA=spA; //A(1) 引用計數1->2
}
//spA離開作用域,引用計數2->1!=0,並不會調用A的析構函數
程序的運行結果爲:
A create!
spA
和spA->m_spA
指向的同一塊內存沒有釋放。一個動態資源由多個智能指針管理,一個智能指針可以提供1個引用計數遞減操作,當所有智能指針都完成了遞減操作後資源被釋放。在本例中A資源的兩個智能指針只有一個完成了引用計數遞減,另一個因爲是類內成員所以始終在等待A資源析構後進行遞減操作,而A資源析構也在等待所有智能指針完成遞減操作。
引用計數所在類沒有析構導致引用計數無法正常減少。再看下邊這個例子:
class A
{
public:
A(int i);
~A() { cout << "A delete!" << endl; }
shared_ptr<B> m_spB;
private:
int m_i;
};
A::A(int i) :m_i(i)
{
cout << "A create!" << endl;
}
class B
{
public:
B(int i);
~B() { cout << "B delete!" << endl; }
shared_ptr<A> m_spA;
private:
int m_i;
};
B::B(int i) :m_i(i)
{
cout << "B create!" << endl;
}
如果這麼調用:
shared_ptr<A> spA=make_shared<A>(1);
shared_ptr<B> spB=make_shared<B>(2);
spA->m_spB=spB;
spB->m_spA=spA;
A
、B
的引用計數都爲2,無論是內部的智能指針遞減操作都在等待析構函數的調用,而各自的析構函數又在等待指針的遞減,出現循環引用。weak_ptr
的出現就是爲了解決循環引用的問題,weak_ptr
是一個觀察者,其特點是不會增加管理對象的引用計數,但是可以lock
方法對獲取一個對應的智能指針,從而完成對動態對象的操作。
二、混合使用智能和普通指針
普通指針不要和智能指針混合使用,當我們講一個普通指針綁定到智能指針時,意味着我們將普通指針管理的資源交給了智能指針。下面是混合使用智能指針和普通指針的例子:
double * pd=new double(1);
shared_ptr<double> spd1(pd);
shared_ptr<double> spd2(pd);
因爲spd1
和spd2
智能指針無法獲知當前內存實際的引用計數,導致了指向同一內存的智能指針狀態的獨立,spd1
和spd2
在離開作用域時都將會釋放一次對應內存,造成“同一內存多次釋放”的問題。
再看另一個混合使用智能和普通的例子:
void use_smartPtr(shared_ptr<int> sp)
{
*sp=4396;
}
內置指針 int * p=new int(3)
不能傳入use_smartPtr
是因爲接收內置指針的構造函數是explict
的,只能通過以下方法構造一個臨時的shared_ptr
:
use_smartPtr(shared_ptr<int>(p));
p
指針初始化了一個臨時共享指針,調用結束之後臨時變量被銷燬,對應內存被回收,任何嘗試使用p
的操作都是未定義的,多次調用use_smartPtr
甚至會出現多次釋放的情況。