請看下面的代碼片斷:html
輸出結果爲:ios
得出結論:第二段程序中,因爲fun()函數中的臨時變量被銷燬,故第二次輸出時,p已經成爲懸垂指針。c++
指向曾經存在的對象,但該對象已經再也不存在了,此類指針稱爲垂懸指針。結果未定義,每每致使程序錯誤,並且難以檢測。程序員
引入智能指針能夠防止垂懸指針出現。通常是把指針封裝到一個稱之爲智能指針類中,這個類中另外還封裝了一個使用計數器,對指針的複製等操做將致使該計數器的值加1,對指針的delete操做則會減1,值爲0時,指針爲NULL。編程
啞指針指傳統的C/C++指針,它只是一個指向,除此之外它不會有其餘任何動做,全部的細節必須程序員來處理,好比指針初始化,釋放等等數組
「野指針」不是NULL指針,是指向「垃圾」內存(不可用內存)的指針。人們通常不會錯用NULL指針,由於用if語句很容易判斷。可是「野指針」是很危險的,if沒法判斷一個指針是正常指針仍是「野指針」。有個良好的編程習慣是避免「野指針」的惟一方法。緩存
有3種狀況可能會形成野指針安全
1:指針變量沒有被初始化。任何指針變量剛被建立時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。因此,指針變量在建立的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它指向合法的內存。less
2:指針p被free或者delete以後,沒有置爲NULL,讓人誤覺得p是個合法的指針。別看free和delete的名字(尤爲是delete),它們只是把指針所指的內存給釋放掉,但並無把指針自己幹掉。一般會用語句if (p != NULL)進行防錯處理。很遺憾,此時if語句起不到防錯做用,由於即使p不是NULL指針,它也不指向合法的內存塊。例函數
另一個要注意的問題:不要返回指向棧內存的指針或引用,由於棧內存在函數結束時會被釋放。
3:指針操做超越了變量的做用範圍。這種狀況讓人防不勝防,示例程序以下:
注意:函數 Test1 在執行語句 p->Func()時,對象 a 已經消失,而 p 是指向 a 的,因此 p 就成了「野指針」。
簡單的說,智能指針是爲了實現相似於Java中的垃圾回收機制。Java的垃圾回收機制使程序員從繁雜的內存管理任務中完全的解脫出來,在申請使用一塊內存區域以後,無需去關注應該什麼時候何地釋放內存,Java將會自動幫助回收。可是出於效率和其餘緣由(可能C++設計者不屑於這種傻瓜氏的編程方式),C++自己並無這樣的功能,其繁雜且易出錯的內存管理也一直爲廣大程序員所詬病。
更進一步地說,智能指針的出現是爲了知足管理類中指針成員的須要。包含指針成員的類須要特別注意複製控制和賦值操做,緣由是複製指針時只複製指針中的地址,而不會複製指針指向的對象。當類的實例在析構的時候,可能會致使垂懸指針問題。
當類中有指針成員時,通常有兩種方式來管理指針成員:一是採用值型的方式管理,每一個類對象都保留一份指針指向的對象的拷貝;另外一種更優雅的方式是使用智能指針,從而實現指針指向的對象的共享。它是指一種實現,能讓指針在離開本身生命週期的時候自動銷燬指向的內容(對象等),這每每用一個對象將指針包裝起來來實現,例如標準庫中的auto_ptr和boost中的智能指針都是智能指針的例子,可是缺點就是沒有帶引用參數。
智能指針的一種通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。智能指針結合了棧的安全性和堆的靈活性,本質上將就是棧對象內部包裝一個堆對象
每次建立類的新對象時,初始化指針並將引用計數置爲1;當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數;對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象),並增長右操做數所指對象的引用計數;調用析構函數時,析構函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。
實現引用計數有兩種經典策略:一是引入輔助類,二是使用句柄類。
例如:
在程序中,類TestPtr對象的任何拷貝、賦值操做都會使多個TestPtr對象共享相同的指針。但在一個對象發生析構時,指針指向的對象將被釋放,從而可能引發懸垂指針。
方法1:引用計數來解決,一個新的問題是引用計數放在哪裏。顯然,不能放在TestPtr類中,由於多個對象共享指針時沒法同步更新引用計數。
當但願每一個TestPtr對象中的指針所指向的內容改變而不影響其它對象的指針所指向的內容時,能夠在發生修改時,建立新的對象,並修改相應的引用計數。這種技術的一個實例就是寫時拷貝(Copy-On-Write)。
缺點是每一個含有指針的類的實現代碼中都要本身控制引用計數,比較繁瑣。特別是當有多個這類指針時,維護引用計數比較困難。
方法2:
爲了不上面方案中每一個使用指針的類本身去控制引用計數,能夠用一個類把指針封裝起來。封裝好後,這個類對象能夠出如今用戶類使用指針的任何地方,表現爲一個指針的行爲。咱們能夠像指針同樣使用它,而不用擔憂普通成員指針所帶來的問題,咱們把這樣的類叫句柄類。在封裝句柄類時,須要申請一個動態分配的引用計數空間,指針與引用計數分開存儲。
STL中auto_ptr只是衆多可能的智能指針之一,auto_ptr所作的事情,就是動態分配對象以及當對象再也不須要時自動執行清理。
注意事項:
一、auto_ptr不能共享全部權。
二、auto_ptr不能指向數組
三、auto_ptr不能做爲容器的成員。
四、不能經過賦值操做來初始化auto_ptr
std::auto_ptr<int> p(new int(42)); //OK
std::auto_ptr<int> p = new int(42); //ERROR
這是由於auto_ptr 的構造函數被定義爲了explicit
五、不要把auto_ptr放入容器
Boost中的智能指針
智能指針是存儲指向動態分配(堆)對象指針的類。除了可以在適當的時間自動刪除指向的對象外,他們的工做機制很像C++的內置指針。智能指針在面對異常的時候格外有用,由於他們可以確保正確的銷燬動態分配的對象。他們也能夠用於跟蹤被多用戶共享的動態分配對象。
事實上,智能指針可以作的還有不少事情,例如處理線程安全,提供寫時複製,確保協議,而且提供遠程交互服務。有可以爲這些ESP (Extremely Smart Pointers)建立通常智能指針的方法,可是並無涵蓋進來。
智能指針的大部分使用是用於生存期控制,階段控制。它們使用operator->和operator*來生成原始指針,這樣智能指針看上去就像一個普通指針。
這樣的一個類來自標準庫:std::auto_ptr。它是爲解決資源全部權問題設計的,可是缺乏對引用數和數組的支持。而且,std::auto_ptr在被複制的時候會傳輸全部權。在大多數狀況下,你須要更多的和/或者是不一樣的功能。這時就須要加入smart_ptr類。
scoped_ptr | <boost/scoped_ptr.hpp> | 簡單的單一對象的惟一全部權。不可拷貝。 |
scoped_array | <boost/scoped_array.hpp> | 簡單的數組的惟一全部權。不可拷貝。 |
shared_ptr | <boost/shared_ptr.hpp> | 在多個指針間共享的對象全部權。 |
shared_array | <boost/shared_array.hpp> | 在多個指針間共享的數組全部權。 |
weak_ptr | <boost/weak_ptr.hpp> | 一個屬於 shared_ptr 的對象的無全部權的觀察者。 |
intrusive_ptr | <boost/intrusive_ptr.hpp> | 帶有一個侵入式引用計數的對象的共享全部權。 |
1. shared_ptr是Boost庫所提供的一個智能指針的實現,shared_ptr就是爲了解決auto_ptr在對象全部權上的侷限性(auto_ptr是獨佔的),在使用引用計數的機制上提供了能夠共享全部權的智能指針.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是能夠拷貝和賦值的,拷貝行爲也是等價的,而且能夠被比較,這意味這它可被放入標準庫的通常容器(vector,list)和關聯容器中(map)。關於shared_ptr的使用其實和auto_ptr差很少,只是實現上有差異,關於shared_ptr的定義就不貼代碼了,覺得內開源,能夠網上找
一、shared_ptr<T> p(new Y);
要了解更多關於auto_ptr的信息,能夠查看more effective c++ 的p158頁條款28
要了解shared_ptr 類模板信息,能夠查看boost 1.37.0中文文檔,並且支持數組的shared_array 類模板
在Boost中的智能指針有:
。scoped_ptr,用於處理單個對象的惟一全部權;與std::auto_ptr不一樣的是,scoped_ptr能夠被複制。
。scoped_array,與scoped_ptr相似,可是用來處理數組的
。shared_ptr,容許共享對象全部權
。shared_array,容許共享數組全部權
scoped_ptr
scoped_ptr智能指針與std::auto_ptr不一樣,由於它是不傳遞全部權的。事實上它明確禁止任何想要這樣作的企圖!這在你須要確保指針任什麼時候候只有一個擁有者時的任何一種情境下都是很是重要的。若是不去使用scoped_ptr,你可能傾向於使用std::auto_ptr,讓咱們先看看下面的代碼:
auto_ptr MyOwnString?
(new string("This is mine to keep!"));
auto_ptr NoItsMine?(MyOwnString?);
cout << *MyOwnString << endl; // Boom
這段代碼顯然將不能編譯經過,由於字符串的全部權被傳給了NoItsMine。這不是std::auto_ptr的設計缺陷—而是一個特性。儘管如此,當你須要MyOwnString達到上面的代碼預期的工做效果的話,你可使用scoped_ptr:
scoped_ptr MyOwnString?
(new string("This is mine to keep for real!"));
// Compiler error - there is no copy constructor.
scoped_ptr TryingToTakeItAnyway?
(MyOwnString?);
scoped_ptr經過從boost::noncopyable繼承來完成這個行爲(能夠查看Boost.utility庫)。不可複製類聲明複製構造函數並將賦值操做符聲明爲private類型。
scoped_array
scoped_array與scoped_ptr顯然是意義等價的,可是是用來處理數組的。在這一點標準庫並無考慮—除非你固然可使用std::vector,在大多數狀況下這樣作是能夠的。
用法和scoped_ptr相似:
typedef tuples::tupleint> ArrayTuple?;
scoped_array MyArray?(new ArrayTuple?[10]);
tuples::get<0>(MyArray?[5]) ="The library Tuples is also part of Boost";
tuple是元素的集合—例如兩倍,三倍,和四倍。Tuple的典型用法是從函數返回多個值。Boost Tuple庫能夠被認爲是標準庫兩倍的擴展,目前它與近10個tuple元素一塊兒工做。支持tuple流,比較,賦值,卸包等等。
當scoped_array越界的時候,delete[]將被正確的調用。這就避免了一個常見錯誤,便是調用錯誤的操做符delete。
這裏有一個你在標準庫中找不到的—引用數智能指針。大部分人都應當有過使用智能指針的經歷,而且已經有不少關於引用數的文章。最重要的一個細節是引用數是如何被執行的—插入,意思是說你將引用計數的功能添加給類,或者是非插入,意思是說你不這樣作。Boost shared_ptr是非插入類型的,這個實現使用一個從堆中分配來的引用計數器。關於提供參數化策略使得對任何狀況都極爲適合的討論不少了,可是最終討論的結果是決定反對聚焦於可用性。但是不要期望討論的結果可以結束。
shared_ptr完成了你所但願的工做:他負責在不使用實例時刪除由它指向的對象(pointee),而且它能夠自由的共享它指向的對象(pointee)。
void PrintIfString?(const any& Any) {
if (const shared_ptr* s =
any_cast >(&Any)) {
cout << **s << endl;
}
}
int main(int argc, char* argv[])
{
std::vector Stuff;
shared_ptr SharedString1?
(new string("Share me. By the way,
Boost.any is another useful Boost
library"));
shared_ptr SharedString2?
(SharedString1?);
shared_ptr SharedInt1?
(new int(42));
shared_ptr SharedInt2?
(SharedInt1?);
Stuff.push_back(SharedString1?);
Stuff.push_back(SharedString2?);
Stuff.push_back(SharedInt1?);
Stuff.push_back(SharedInt2?);
// Print the strings
for_each(Stuff.begin(), Stuff.end(),
PrintIfString?);
Stuff.clear();
// The pointees of the shared_ptr's
// will be released on leaving scope
// shared_ptr的pointee離開這個範圍後將被釋放
return 0;
}
any庫提供了存儲全部東西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator桌面My%20Documents新建 CUJhtml20.04karlsson%22%20l"[4]。在包含類型中須要的是它們是可拷貝構造的(CopyConstructible),析構函數這裏絕對不能引起,他們應當是可賦值的。咱們如何存儲和傳遞「全部事物」?無區別類型(讀做void*)能夠涉及到全部的事物,但這將意味着將類型安全(與知識)拋之腦後。any庫提供類型安全。全部知足any需求的類型都可以被賦值,可是解開的時候須要知道解開類型。any_cast是解開由any保存着的值的鑰匙,any_cast與dynamic_cast的工做機制是相似的—指針類型的類型轉換經過返回一個空指針成功或者失敗,所以賦值類型的類型轉換拋出一個異常(bad_any_cast)而失敗。
shared_array與shared_ptr做用是相同的,只是它是用於處理數組的。
shared_array MyStrings?( new Base[20] );
深刻shared_ptr實現
建立一個簡單的智能指針是很是容易的。可是建立一個可以在大多數編譯器下經過的智能指針就有些難度了。而建立同時又考慮異常安全就更爲困難了。Boost::shared_ptr這些全都作到了,下面即是它如何作到這一切的。(請注意:全部的include,斷開編譯器處理,以及這個實現的部份內容被省略掉了,但你能夠在Boost.smart_ptr當中找到它們)。
首先,類的定義:很顯然,智能指針是(幾乎老是)模板。
template class shared_ptr {
公共接口是:
explicit shared_ptr(T* p =0) : px(p) {
// fix: prevent leak if new throws
try { pn = new long(1); }
catch (...) { checked_delete(p); throw; }
}
如今看來,在構造函數當中兩件事情是容易被忽略的。構造函數是explicit的,就像大多數的構造函數同樣能夠帶有一個參數。另一個值得注意的是引用數的堆分配是由一個try-catch塊保護的。若是沒有這個,你獲得的將是一個有缺陷的智能指針,若是引用數沒有可以成功分配,它將不能正常完成它本身的工做。
~shared_ptr() { dispose(); }
析構函數執行另一個重要任務:若是引用數降低到零,它應當可以安全的刪除指向的對象(pointee)。析構函數將這個重要任務委託給了另一個方法:dispose。
void dispose() { if (—*pn == 0)
{ checked_delete(px); delete pn; } }
正如你所看到的,引用數(pn)在減小。若是它減小到零,checked_delete在所指對象 (px)上被調用,然後引用數(pn)也被刪除了。
那麼,checked_delete執行什麼功能呢?這個便捷的函數(你能夠在Boost.utility中找到)確保指針表明的是一個完整的類型。在你的智能指針類當中有這個麼?
這是第一個賦值運算符:
template shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}
這是成員模版,若是不是這樣,有兩種狀況:
1. 若是沒有參數化複製構造函數,類型賦值Base = Derived無效。
2. 若是有參數化複製構造函數,類型賦值將生效,但同時建立了一個沒必要要的臨時smart_ptr。
這再一次的展現給你爲何不該當加入你本身的智能指針的一個很是好的緣由—這些都不是很明顯的問題。
賦值運算符的實際工做是由share函數完成的:
void share(T* rpx, long* rpn) {
if (pn = rpn) { // Q: why not px = rpx?
// A: fails when both == 0
++*rpn; // done before dispose() in case
// rpn transitively dependent on
// *this (bug reported by Ken Johnson)
dispose();
px = rpx;
pn = rpn;
}
}
須要注意的是自我賦值(更準確地說是自我共享)是經過比較引用數完成的,而不是經過指針。爲何這樣呢?由於它們二者均可以是零,但不必定是同樣的。
template shared_ptr
(const shared_ptr& r) : px(r.px) { // never throws
++*(pn = r.pn);
}
這個版本是一個模版化的拷貝構造和函數。能夠看看上面的討論來了解爲何要這樣作。
賦值運算符以及賦值構造函數在這裏一樣也有一個非模版化的版本:
shared_ptr(const shared_ptr& r) :
// never throws
px(r.px) { ++*(pn = r.pn); }
shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}
reset函數就像他的名字那樣,從新設置所指對象(pointee)。在將要離開做用域的時候,若是你須要銷燬所指對象(pointee)它將很是方便的幫你完成,或者簡單的使緩存中的值失效。
void reset(T* p=0) {
// fix: self-assignment safe
if ( px == p ) return;
if (—*pn == 0)
{ checked_delete(px); }
else { // allocate new reference
// counter
// fix: prevent leak if new throws
try { pn = new long; }
catch (...) {
// undo effect of —*pn above to
// meet effects guarantee
++*pn;
checked_delete(p);
throw;
} // catch
} // allocate new reference counter
*pn = 1;
px = p;
} // reset
這裏仍然請注意避免潛在的內存泄漏問題和保持異常安全的處理手段。
這樣你就有了使得智能指針發揮其「智能」的運算符:
// never throws
T& operator*() const { return *px; }
// never throws
T* operator->() const { return px; }
// never throws
T* get() const { return px; }
這僅僅是一個註釋:有的智能指針實現從類型轉換運算符到T*的轉換。這不是一個好主意,這樣作常會使你所以受到傷害。雖然get在這裏看上去很不舒服,但它阻止了編譯器同你玩遊戲。
我記得是Andrei Alexandrescu說的:「若是你的智能指針工做起來和啞指針沒什麼兩樣,那它就是啞指針。」簡直是太對了。
這裏有一些很是好的函數,咱們就拿它們來做爲本文的結束吧。
long use_count() const
{ return *pn; } // never throws
bool unique() const
{ return *pn == 1; } // never throws
函數的名字已經說明了它的功能了,對麼?
關於Boost.smart_ptr還有不少應當說明的(好比std::swap和std::less的特化,與std::auto_ptr榜定在一塊兒確保兼容性以及便捷性的成員,等等),因爲篇幅限制不能再繼續介紹了。詳細內容請參考Boost distribution ()的smart_ptr.hpp。即便沒有那些其它的內容,你不認爲他的確是一個很是智能的指針麼?