“智能指針”的成長
智能指針是什麽,他的真面目就是一個類生成的對象,這個類中包含了基本的重載->、*等一些指針形態的用法,最主要的是這個類裏面有一個指針參數:所有智能指針類中都有一個explicit構造函數,以指針作為參數。比如auto_ptr的類模板原型為:
template<class T>
class auto_ptr{
explicit auto_ptr(T* p = 0);
};
代碼中構造函數用了explicit關鍵字主要是防止隱式轉換,舉個例子:
auto_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; //error
pd = shared_ptr<double>(p_reg); //error
shared_ptr<double> pshared = p_reg; //error
shared_ptr<double> pshared(p_reg);
上面解釋了一下智能指針的本質是什麽,那它有什麽作用呢,看代碼看不出來,但仔細想,作為一個類對象,創建時需要構造,那待生命周期結束,也是需要自動析構的,所以就帶出了他的功能,就是為了保證其指向的New出來的對象,可以在生命周期結束時自動Delete,作為程序員我們都知道,Delete這個東西我們經常忘寫不說,也在很多return的地方忽略了寫Delete,這就導致了內存泄漏,很致命。
所以C++就提供了一種智能指針的模式去防止這種現象,現有最常用的智能指針,分別有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr。這6個智能指針都有各自的使用範疇,話不多說,研究下就知道。
⑴ std::auto_ptr
首先auto_ptr這個智能指針,屬於STL,同屬於STL的還有unique_ptr、shared_ptr等,感興趣的可以去查查,但本文就不進行介紹了。先來看一段代碼
class Simple {
public:
Simple(int param = 0) {
number = param;
std::cout << "Simple: " << number << std::endl;
}
~Simple() {
std::cout << "~Simple: " << number << std::endl;
}
void PrintSomething() {
std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
}
std::string info_extend;
int number;
};
void fun()
{
std::auto_ptr<Simple> ptr (new Simple(1));
if(ptr.get())
{
ptr->PrintSomething();
ptr.get()->info_extend = "Hello";
(*prt)->info_extend = "World";
}
}
上一段代碼運行的結果為:
Simple:1
PrintSometing:
~Simple:1
這是最標準的一種智能指針的使用方法,對於auto_ptr這種智能指針也是完全能駕馭的,但假如我們在代碼中加入另外幾句
std::auto_ptr<Simple> ptr2;
ptr2 = ptr;
pt2->PrintSomething();
pt1->PrintSomething(); // 崩潰
如果將上面的代碼加到程序中就會崩潰,為什麽呢?看源碼中,重載=時,進行了
reset(_Right.release());
繼續向裏執行release的代碼
可以看到,這裏是將原本的指針控制權交給了別人,自身已經被置為NULL了。所以我們進行調用,不崩潰等什麽呢?
然後加另一端代碼:
if (ptr.get()) {
ptr.release();
}
我們可以直接調用release函數,這就很尷尬了,直接返回值為當前指向的控制權,但沒有指針接收,直接丟棄了,這就更尷尬了,導致只進行了構造,沒有析構。。所以真要使用release函數的話,必須這樣
Simple* ptr2 = ptr.release();
delete ptr2;
這惡心的用法總給人一種多此一舉的做法,所以我release函數的直接使用是沒有任何意義的。對於這種手動的釋放內存,直接調用
ptr.reset();
所以我們對auto_ptr進行一個總結:
1,盡量不要使用"="對智能指針進行賦值,如果使用,那先前對象就時一個空指針的。
2,release函數單獨使用沒有任何意義,不會釋放對象,還有可能導致對象丟失。他的作用僅僅是返回所有權。
3,不要把智能指針對象當做參數傳遞進入函數,因為也會調用release,導致調用完函數,回來對象已經被析構。
上述3個僅僅是個人發現的,對於auto_ptr本身就有很多不合理的地方,雖然還有人使用,但具體項目中用的已經不多了,所以就在auto_ptr的基礎上,增加了各種其他的智能指針,用以修復這個使用不合理的地方。
⑵ boost::scoped_ptr
scoped_ptr屬於boost庫,他與auto_ptr相比,也是避免了上述幾個問題,主要的原因就是scoped的設計為獨享所有權。主要的體現就是調用release函數和‘=’的時候是會報錯的。雖然表面上的確是避免了auto_ptr中出現的問題,但種感覺是一種逃避的方法,那就有了下面的另一種智能指針。
⑶ boost::shared_ptr
scoped_ptr屬於boost庫,與上述的scoped_ptr相比,它添加了共享所有權的功能,也就是可以使用‘=’方法,關於做法,原理就是在類中使用了一個引用計數,當進行賦值的時候對該數字加1,寫一段代碼看看:
void TestSharedPtr(boost::shared_ptr<Simple> ptr)
{
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
}
boost::shared_ptr<Simple> ptr(new Simple(1));
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
{
boost::shared_ptr<Simple> ptr2 = ptr;
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
TestSharedPtr(ptr);
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
}
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
這一段代碼運行下來的打印結果是:
TestSharedPtr2 UseCount: 1
TestSharedPtr2 UseCount: 2
TestSharedPtr2 UseCount: 3
TestSharedPtr2 UseCount: 2
TestSharedPtr2 UseCount: 1
大概解釋一下,在開始只有一個ptr時,打印出來的引用計數為1,當另外使用一個ptr2時,再次打印出來為2,意思就是當前共有兩個智能指針同時使用當前對象。進入函數後,又進行了一次拷貝構造到臨時變量,所以打印值又變成3,後續退出函數和退出ptr2的生命周期,引用計數的值各剪了1,當代碼執行完畢後,引用u計數值歸0,調用析構函數時使用Delete釋放空間。
⑷ boost::scoped_array
以數組的方式管理多個對象,但屬性與scoped_ptr完全一致,獨享所有權,而其使用方法也與數組沒有太大的區別:
boost::scoped_array<Simple> ptr_array(new Simple[2]);
ptr_array[0].PrintSomething();
⑸ boost::shared_array
以數組的方式管理多個對象,屬性與shared_ptr王權一致,共享所有權,內部使用引用計數
⑹ boost::weak_ptr
weak_ptr是一中比較特殊的智能指針,其應用的場景比較單一,主要是對shared_ptr進行觀察的一種對象,也就是說用weak_ptr類型的對象對shared_ptr類型的對象進行引用,是可以正常訪問,但不會改變後者的引用計數值,當後者被銷毀後,前者也就不能使用了。現在說說它的應用場景,如果子類中有一個shared_ptr類型的對象,那虛基類中就可以定義一個weak_ptr類型的對象,通過訪問基類的weak_ptr對象是否為空,就可以判斷子類是否對自己進行賦值。
本文有部分函數名和編碼方式是采用網上其他博主的風格(因為覺得這種講解方法特別清晰,先簡單介紹,然後上代碼直接理解),所以有些抄襲的嫌疑,但全部均本人理解,並重新編碼之後寫上去的,很多地方也是本人通過理解源碼,寫出來的部分東西,如果有誤,請及時溝通修正。
“智能指針”的成長