shared_ptr是一種智能指針(smart pointer)。shared_ptr的作用有如同指針,但會記錄有多少個shared_ptrs共同指向一個對象。
shared_ptr是C++非常重要的一個防止內存泄露的設計
作用:
1.1刪除共享對象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include "boost/shared_ptr.hpp" #include <cassert> class A { boost::shared_ptr< int > no_; public : A(boost::shared_ptr< int > no) : no_(no) {} void value( int i) { *no_=i; } }; class B { boost::shared_ptr< int > no_; public : B(boost::shared_ptr< int > no) : no_(no) {} int value() const { return *no_; } }; int main() { boost::shared_ptr< int > temp( new int (14)); A a(temp); B b(temp); a.value(28); assert (b.value()==28); } |
1.2標準容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include "boost/shared_ptr.hpp" #include <vector> #include <iostream> class A { public : virtual void sing()=0; protected : virtual ~A() {}; }; class B : public A { public : virtual void sing() { std::cout << "Do re mi fa so la" ; } }; boost::shared_ptr<A> createA() { boost::shared_ptr<A> p( new B()); return p; } int main() { typedef std::vector<boost::shared_ptr<A> > container_type; typedef container_type::iterator iterator; container_type container; for ( int i=0;i<10;++i) { container.push_back(createA()); } std::cout << "The choir is gathered: \n" ; iterator end=container.end(); for (iterator it=container.begin();it!=end;++it) { (*it)->sing(); } } |
現在考慮如下一個資源從創建到銷燬的過程:
{
int* pInt = new int(14);
...... // 此處有一系列代碼
delete pInt;
}
如果程序在......這些代碼的執行中跳出了異常。這樣導致程序不會運行到delete pInt2這裏,這時資源泄露了。
如何防止這種情況呢? 由此我們聯想到類的構造和和類離開作用域時的自動析構.這就是資源獲取即初始化技術。資源獲取即初始化(RAII, Resource Acquisition Is Initialization)是指,當你
獲得一個資源的時候,不管這個資源是對象、內存、文件句柄或者其它什麼,你都要在一個對象的構造函數中獲得它, 並且在該對象的析構函數中釋放它。實現這種功能的類,我們就說它採用了"資源
獲取即初始化(RAII)"的方式。這樣的類常常被稱爲封裝類。
下面爲一個最簡單的RAII:
class CMySingleLock
{
public:
CMySingleLock()
{
InitializeCriticalSection(&m_CritSec);
EnterCriticalSection(&m_CritSec);
}
~CMySingleLock (void)
{
LeaveCriticalSection(&m_CritSec);
DeleteCriticalSection(&m_CritSec);
}
private:
CRITICAL_SECTION m_CritSec;
};
這樣就可以這樣調用:
{
CMySingleLock mySingleLock;
....處理變量
} // 此處mySingleLock已經離開作用域,自動解鎖
很多程序用到了該技術,如mutex, criticalSection. 這樣,當你創建資源的時候就立即將它放入封裝類對象構造函數(new出來的指針立即放入shared_ptr析造函數裏), 當該對象離開作用域時,
對象析構函數會自動銷燬資源(shared_ptr對象離開作用域時,會自動銷燬指向的資源).
1.2
shared_ptr是典型的資源獲取即初始化技術。不過shared_ptr採用了更復雜一點RAII方式,因爲它實現了引用計數。當創建資源的時候將它放入一個shared_ptr, shared_ptr內部記錄對這種資源的引用次數爲1次。當這個shared_ptr對象離開作用域時,引用次數減1,shared_ptr對象析構時,檢查到引用次數爲0了,就銷燬資源:
{
boost::shared_ptr pInt(new int(14));
assert(pInt.use_count() == 1); // new int(14)這個指針被引用1次
...... // 此處有一系列代碼
} //pInt離開作用域, 所以new int(14)被引用次數爲0. 指針被銷燬。防止了
此處及以下assert代碼都會判斷成功。如果......跳出異常。那麼pInt也離開了作用域,指針照常還是被銷燬,所以智能指針可以有效防止資源泄露。
考慮更多次的引用情況:
{
boost::shared_ptr pInt2;
assert(pInt2.use_count() == 0); // temp2還沒有引用指針
{
boost::shared_ptr pInt1(new int(14));
assert(pInt1.use_count() == 1); // new int(14)這個指針被引用1次
pInt2 = pInt1;
assert(pInt1.use_count() == 2); // new int(14)這個指針被引用2次
assert(pInt2.use_count() == 2);
} //pInt1離開作用域, 所以new int(14)被引用次數-1
assert(pInt2.use_count() == 1);
} // pInt2離開作用域,引用次數-1,現在new int(14)被引用0次,所以銷燬它
不管資源曾經被多少次引用。當它被引用0次時就會銷燬。
1.3
在shard_ptr使用中經常會發現,一個對象會有兩次被析構的情況。其實這種是因爲那個對象指針被兩次當成shard_ptr構造函數裏的參數。一定要避免這種現象。考慮如下代碼:
{
int* pInt = new int(14);
boost::shared_ptr temp1(pInt);
assert(temp1.use_count() == 1); // 用一個指針初始化temp1,temp1認爲pInt只被它擁有。所以這個指針被引用1次
boost::shared_ptr temp2(pInt); // 用一個指針初始化temp2,temp2認爲pInt只被它擁有。所以這個指針被引用1次
assert(temp2.use_count() == 1);
} // temp1,temp2都離開作用域,它們都銷燬pInt. pInt被銷燬了兩次!系統終於崩潰了 -_-
正確的做法是將原始指針賦給智能指針後,以後的操作都要針對智能指針了.
{
boost::shared_ptr temp1(new int(14)); // 資源獲取即初始化
assert(temp1.use_count() == 1);
boost::shared_ptr temp2(temp1);
assert(temp2.use_count() == 2);
} // temp1,temp2都離開作用域,引用次數變爲0,指針被銷燬
1.4
如果資源的創建銷燬不是以new,delete的方式創建銷燬怎麼辦?shared_ptr也可以指定刪除器:
// FileCloser.h FileCloser刪除器
class FileCloser
{
public:
void operator()(FILE *pf)
{
if (pf)
{
fclose(pf);
}
}
};
// 某實現文件
{
boost::shared_ptr fp(fopen(pszConfigFile, "r"), FileCloser()); // 指定調用FileCloser函數對象銷燬資源
}
1.5
shared_ptr已經被即將到來的標準庫技術報告所採納,將成爲tr1中的一員。爲了以後更好的移植現有代碼到C++新標準中。可以使用一個namespace的一個小技巧,在頭文件StdAfx.h中聲明(參考
Effective C++ Item 54):
namespace std
{
namespace tr1 = ::boost; // namespace std::tr1 is an alias
} // for namespace boost
這樣就可以用如下方法寫代碼:
std::tr1::shared_ptr pInt(new int(14));