內存管理需要注意的點
- 內存泄漏
- 野指針
- 訪問越界
爲了避免這些問題,智能指針採用RAII機制 —資源獲取既初始化
- 所有初始化操作移到對象的構造函數中
- 所有的釋放操作都放在對象的析構函數裏
- 適當的異常處理代碼來應付對象構造期間丟出的異常(分配內存的時候)
好處:對象創建後,用戶能開始正確使用對象,不用擔心對象的有效性或者是否還要作進一步的初始化操作。
scoped_ptr 不能拷貝,賦值。
侯捷大師說過,源碼面前,了無祕密
於是就去找一下源碼,源碼中將 拷貝賦值函數設置爲 private,並且函數裏面爲空語句。
不適用於stl 類似的容器中
weak_ptr weak_ptr 可以說具有觀察權。
準確的說不能算作一種智能指針,它的出現是用來彌補 share_ptr 的缺點,例如循環引用。
它有一個lock成員函數,可以將弱引用轉化爲 share_ptr。
其中最常用的就是share_ptr了,它也被收入到了c++ 11 標準中
- share_ptr 用了設計模式中的 代理模式,用share_ptr 代理指針,所以它獲得了指針相關的操作,比如解引用等等
- 不可以使用變址操作符(因爲指針越界大部分是因爲變址操作符導致的)。
- share_ptr 不需要手動的調用類似release 方法。
- share_ptr 只會所有權轉移 --rest()
share_ptr 源碼摘要
template<class T>
class shared_ptr
{
//創建一個持有空指針的share_ptr,use_count = 0 && get() == NULL
shared_ptr() BOOST_NOEXCEPT : px( 0 ), pn() // never throws in 1.30+{}
//獲得一個指向類型T的指針p的管理權,Y 必須能夠轉換爲T類型
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
//作用同上,增加了一個構造函數D,是一個仿函數對象,代表刪除器
template<class Y, class D>
shared_ptr( Y * p, D d ): px( p ), pn( p, d )
//析構函數:資源引用計數減1 同時判斷引用計數爲0,並且 管理的資源不爲NULL,並且判斷是否有刪除器,如果有刪除器,調用刪除器,如果沒有刪除器,調用delete
~shared_ptr();
};
這裏爲什麼要有刪除器呢?
例如:fopen(),fclose() 操作打開的資源。這是delete 沒用的。
構造函數獲得管理權,
拷貝賦值操作 共享管理權
至於shared_ptr 的成員函數,這裏就只說一下reset();
void reset();
template<class Y> void reset(Y* p);
template<class Y,class D> void reset(Y* p);
reset(): 如果該share_ptr 擁有某個管理權的話,它會將引用計數減1,如果資源計數器爲0,並且指針不爲NULL,刪除原來共享的資源。將指針賦爲 NULL
template void reset(Y* p); --轉移管理權
首先判斷該share_ptr 是否擁有管理權,如果有的話,,它會將引用計數減1,如果資源計數器爲0,它會釋放掉原來所指向的資源或指針,讓他delete,然後它會將它內部的T 類型的指針,設置爲參數 Y 的指針,這樣就獲得了 Y 類型 的 p 指針的管理權,然後將引用計數設爲1。
shared_ptr 使用時出現的常見問題
1.shared_ptr 多次引用同一內存數據導致程序崩潰
No code , No BB
class Foo
{
public:
Foo(string s) : m_s(s) {}
~Foo()
{
cout << "Foo 開始析構" << endl;
}
private:
string m_s;
};
int main()
{
Foo* pFoo = new Foo("test");
shared_ptr<Foo> p1(pFoo);
shared_ptr<Foo> p2(pFoo);
return 0;
}
運行結果:
可以看到,將Foo() 析構了兩次。
解決方案:
1.使用匿名 new操作,資源獲取既初始化
shared_ptr<Foo> p1(new Foo("test"));
2.使用boost 工廠函數
2.shared_ptr 循環引用導致內存泄露
class B; // 前置聲明
class A {
public:
A()
{
cout << "A 構造" << endl;
}
~A()
{
cout << "A 析構" << endl;
}
shared_ptr<B> ptr;
};
class B {
public:
B()
{
cout << "B 構造" << endl;
}
~B()
{
cout << "B 析構 " << endl;
}
shared_ptr<A> ptr;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa -> ptr = pb; // 1
pb -> ptr = pa; //2
return 0;
}
運行結果:
結果沒有調用析構函數。
什麼原因呢?
當執行 1 時,pb 指針的引用計數加 1
當執行 2 時,pa 指針的引用計數加 1
離開主函數時,兩個指針的引用計數減一,不爲 0 ,所以不調用析構函數。
解決方法:
weak_ptr
1.從上面的例子可以看出,引用計數是一種很便利的內存管理,但是有一個缺點,那就是不能管理循環引用或自引用對象,然後就引入了 weak_ptr 。
2.它是與shared_ptr 同時使用的,它更像是shared_ptr 的助手而不是智能指針,因爲它不具備普通指針的行爲,沒有重載operaotr * 和 -> 操作符。這是特意的。這樣他就不能共享指針,不能操作資源,這正是它"弱"的原因,它最大的作用是協助shared_ptr 工作,像旁觀者那樣觀察資源的使用情況
3.weak_ptr 可以從一個shared_ptr 或另外一個weal_ptr 構造,從而獲得資源的觀察權,但weak_ptr 並沒有共享資源,它的構造並不會引起引用計數的增加,同事它的析構也不會引起引用計數的減少,它僅僅是觀察者。
4.weak_ptr 的lock 成員函數 的返回值是一個shared_ptr 類型的指針。
make_shared< T >模板工廠函數
1.用於不需要使用刪除器的情況下(僅僅使用 new /delete 進行內存分配和析構的類上面)
有數shared_ptr 顯式的消除了delete 操作符的調用,因此使用該函數也可以顯式的消除了 new 操作符的調用
2.調用該函數比直接創建shared_ptr 對象的方式快且高效,因爲它內部僅分配一次內存,消除了shared_ptr 構造時的開銷,建議在滿足情況的基礎上儘量使用該函數
3.它具有可變模板參數特性,如果c++ 編譯器支持c++ 11 的可變參數模板特性,那麼該工廠函數的參數數量沒有限制,否則它只能接受最多 10 個參數被傳遞到 T 的構造函數參數中去。