標準auto_ptr智能指針機制很多人都知道,但很少使用它。這真是個遺憾,因為auto_ptr優(yōu)雅地解決了C++設計和編碼中常見的問題,正確地使用它可以生成健壯的代碼。本文闡述了如何正確運用auto_ptr來讓你的代碼更加安全——以及如何避免對auto_ptr危險但常見的誤用,這些誤用會引發(fā)間斷性發(fā)作、難以診斷的bug。
1.為什么稱它為“自動”指針? auto_ptr只是眾多可能的智能指針之一。許多商業(yè)庫提供了更復雜的智能指針,用途廣泛而令人驚異,從管理引用的數(shù)量到提供先進的代理服務??梢园褬藴蔆++ auto_ptr看作智能指針的Ford Escort(elmar注:考試大提示:可能指福特的一種適合家居的車型):一個簡易、通用的智能指針,它不包含所有的小技巧,不像專用的或高性能的智能指針那么奢華,但是它可以很好的完成許多普遍的工作,它很適合日常性的使用。
auto_ptr所做的事情,就是動態(tài)分配對象以及當對象不再需要時自動執(zhí)行清理。這里是一個簡單的代碼示例,沒有使用auto_ptr所以不安全:
// 示例 1(a): 原始代碼 //
void f()
{
T* pt( new T );
...代碼...
delete pt;
}
如果f()函數(shù)只有三行并且不會有任何意外,這么做可能挺好的。但是如果f()從不執(zhí)行delete語句,或者是由于過早的返回,或者是由于執(zhí)行函數(shù)體時拋出了異常,那么這個被分配的對象就沒有被刪除,從而我們產生了一個經(jīng)典的內存泄漏。
能讓示例1(a)安全的簡單辦法是把指針封裝在一個“智能的”類似于指針的對象里,這個對象擁有這個指針并且能在析構時自動刪除這個指針所指的對象。因為這個智能指針可以簡單的當成一個自動的對象(這就是說,它出了作用域時會自動毀滅),所以很自然的把它稱之為“智能”指針:
// 示例 1(b): 安全代碼, 使用了auto_ptr //
void f()
{
auto_ptr pt( new T );
...代碼...
} // 當pt出了作用域時析構函數(shù)被調用, 從而對象被自動刪除
現(xiàn)在代碼不會泄漏T類型的對象,不管這個函數(shù)是正常退出還是拋出了異常,因為pt的析構函數(shù)總是會在出棧時被調用。清理會自動進行。
最后,使用一個auto_ptr就像使用一個內建的指針一樣容易,而且如果想要“撤銷”資源,重新采用手動的所有權,我們只要調用release():
// 示例 2: 使用一個 auto_ptr //
void g()
{
T* pt1 = new T; // 現(xiàn)在,我們有了一個分配好的對象
// 將所有權傳給了一個auto_ptr對象
auto_ptr pt2( pt1 );
// 使用auto_ptr就像我們以前使用簡單指針一樣
*pt2 = 12; // 就像 "*pt1 = 12;"
pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"
// 用get()來獲得指針的值
assert( pt1 == pt2.get() );
// 用release()來撤銷所有權
T* pt3 = pt2.release();
// 自己刪除這個對象,因為現(xiàn)在
// 沒有任何auto_ptr擁有這個對象
delete pt3;
} // pt2不再擁有任何指針,所以不要 // 試圖刪除它...ok,不要重復刪除
最后,我們可以使用auto_ptr的reset()函數(shù)來重置auto_ptr使之擁有另一個對象。如果這個auto_ptr已經(jīng)擁有了一個對象,那么,它會先刪除已經(jīng)擁有的對象,因此調用reset()就如同銷毀這個auto_ptr,然后新建一個并擁有一個新對象:
// 示例 3: 使用reset() //
void h()
{
auto_ptr pt( new T(1) );
pt.reset( new T(2) ); // 刪除由"new T(1)"分配出來的第一個T
} // 最后,pt出了作用域, // 第二個T也被刪除了
auto_ptr用法:
1.需要包含頭文件
2.Constructor:explicit auto_ptr(X* p = 0) throw(); 將指針p交給auto_ptr對象托管
3.Copy constructor: auto_ptr(const auto_ptr&) throw(); template auto_ptr(const auto_ptr& a) throw(); 指針的托管權會發(fā)生轉移
4.Destructor: ~auto_ptr(); 釋放指針p指向的空間
5.提供了兩個成員函數(shù) X* get() const throw();//返回保存的指針,對象中仍保留指針 X* release() const throw();//返回保存的指針,對象中不保留指針
auto_ptr實現(xiàn)關鍵點 1.利用特點”棧上對象在離開作用范圍時會自動析構”
2.對于動態(tài)分配的內存,其作用范圍是程序員手動控制的,這給程序員帶來了方便但也不可避免疏忽造成的內存泄漏,畢竟只有編譯器是最可靠的。
3.auto_ptr通過在棧上構建一個對象a,對象a中wrap了動態(tài)分配內存的指針p,所有對指針p的操作都轉為對對象a的操作。而在a的析構函數(shù)中會自動釋放p的空間,而該析構函數(shù)是編譯器自動調用的,無需程序員操心。
多說無益,看一個最實用的例子:
#include
#include
using namespace std;
class TC
{
public:
TC(){cout<<"TC()"< ~TC(){cout<<"~TC()"< };
void foo(bool isThrow)
{
auto_ptr pTC(new TC); //方法2
//TC *pTC = new TC; //方法1
try
{
if(isThrow)
throw "haha";
}
catch(const char* e)
{
//delete pTC; //方法1
throw;
}
//delete pTC; //方法1
}
int main()
{
try
{
foo(true);
}
catch(...)
{
cout<<"caught"< }
system("pause");
}
1.如果采用方案1,那么必須考慮到函數(shù)在因throw異常的時候釋放所分配的內存。 這樣造成的結果是在每個分支處都要很小心的手動 delete pTC;
2.如果采用方案2,那就無需操心何時釋放內存,不管foo()因何原因退出, 棧上對象pTC的析構函數(shù)都將調用,因此托管在之中的指針所指的內存必然安全釋放。
至此,智能指針的優(yōu)點已經(jīng)很明了了。
但是要注意使用中的一個陷阱,那就是指針的托管權是會轉移的。 例如在上例中,如果 auto_ptr pTC(new TC); auto_ptr pTC1=pTC; 那么,pTC1將擁有該指針,而pTC沒有了,如果再用pTC去引用,必然導致內存錯誤。
要避免這個問題,可以考慮使用采用了引用計數(shù)的智能指針,例如boost::shared_ptr等
auto_ptr不會降低程序的效率,但auto_ptr不適用于數(shù)組,auto_ptr根本不可以大規(guī)模使用。 shared_ptr也要配合weaked_ptr,否則會很容易觸發(fā)循環(huán)引用而永遠無法回收內存。理論上,合理使用容器加智能指針,C++可以完全避免內存泄露,效率只有微不足道的下降。
1.為什么稱它為“自動”指針? auto_ptr只是眾多可能的智能指針之一。許多商業(yè)庫提供了更復雜的智能指針,用途廣泛而令人驚異,從管理引用的數(shù)量到提供先進的代理服務??梢园褬藴蔆++ auto_ptr看作智能指針的Ford Escort(elmar注:考試大提示:可能指福特的一種適合家居的車型):一個簡易、通用的智能指針,它不包含所有的小技巧,不像專用的或高性能的智能指針那么奢華,但是它可以很好的完成許多普遍的工作,它很適合日常性的使用。
auto_ptr所做的事情,就是動態(tài)分配對象以及當對象不再需要時自動執(zhí)行清理。這里是一個簡單的代碼示例,沒有使用auto_ptr所以不安全:
// 示例 1(a): 原始代碼 //
void f()
{
T* pt( new T );
...代碼...
delete pt;
}
如果f()函數(shù)只有三行并且不會有任何意外,這么做可能挺好的。但是如果f()從不執(zhí)行delete語句,或者是由于過早的返回,或者是由于執(zhí)行函數(shù)體時拋出了異常,那么這個被分配的對象就沒有被刪除,從而我們產生了一個經(jīng)典的內存泄漏。
能讓示例1(a)安全的簡單辦法是把指針封裝在一個“智能的”類似于指針的對象里,這個對象擁有這個指針并且能在析構時自動刪除這個指針所指的對象。因為這個智能指針可以簡單的當成一個自動的對象(這就是說,它出了作用域時會自動毀滅),所以很自然的把它稱之為“智能”指針:
// 示例 1(b): 安全代碼, 使用了auto_ptr //
void f()
{
auto_ptr
...代碼...
} // 當pt出了作用域時析構函數(shù)被調用, 從而對象被自動刪除
現(xiàn)在代碼不會泄漏T類型的對象,不管這個函數(shù)是正常退出還是拋出了異常,因為pt的析構函數(shù)總是會在出棧時被調用。清理會自動進行。
最后,使用一個auto_ptr就像使用一個內建的指針一樣容易,而且如果想要“撤銷”資源,重新采用手動的所有權,我們只要調用release():
// 示例 2: 使用一個 auto_ptr //
void g()
{
T* pt1 = new T; // 現(xiàn)在,我們有了一個分配好的對象
// 將所有權傳給了一個auto_ptr對象
auto_ptr
// 使用auto_ptr就像我們以前使用簡單指針一樣
*pt2 = 12; // 就像 "*pt1 = 12;"
pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"
// 用get()來獲得指針的值
assert( pt1 == pt2.get() );
// 用release()來撤銷所有權
T* pt3 = pt2.release();
// 自己刪除這個對象,因為現(xiàn)在
// 沒有任何auto_ptr擁有這個對象
delete pt3;
} // pt2不再擁有任何指針,所以不要 // 試圖刪除它...ok,不要重復刪除
最后,我們可以使用auto_ptr的reset()函數(shù)來重置auto_ptr使之擁有另一個對象。如果這個auto_ptr已經(jīng)擁有了一個對象,那么,它會先刪除已經(jīng)擁有的對象,因此調用reset()就如同銷毀這個auto_ptr,然后新建一個并擁有一個新對象:
// 示例 3: 使用reset() //
void h()
{
auto_ptr
pt.reset( new T(2) ); // 刪除由"new T(1)"分配出來的第一個T
} // 最后,pt出了作用域, // 第二個T也被刪除了
auto_ptr用法:
1.需要包含頭文件
2.Constructor:explicit auto_ptr(X* p = 0) throw(); 將指針p交給auto_ptr對象托管
3.Copy constructor: auto_ptr(const auto_ptr&) throw(); template
4.Destructor: ~auto_ptr(); 釋放指針p指向的空間
5.提供了兩個成員函數(shù) X* get() const throw();//返回保存的指針,對象中仍保留指針 X* release() const throw();//返回保存的指針,對象中不保留指針
auto_ptr實現(xiàn)關鍵點 1.利用特點”棧上對象在離開作用范圍時會自動析構”
2.對于動態(tài)分配的內存,其作用范圍是程序員手動控制的,這給程序員帶來了方便但也不可避免疏忽造成的內存泄漏,畢竟只有編譯器是最可靠的。
3.auto_ptr通過在棧上構建一個對象a,對象a中wrap了動態(tài)分配內存的指針p,所有對指針p的操作都轉為對對象a的操作。而在a的析構函數(shù)中會自動釋放p的空間,而該析構函數(shù)是編譯器自動調用的,無需程序員操心。
多說無益,看一個最實用的例子:
#include
#include
using namespace std;
class TC
{
public:
TC(){cout<<"TC()"<
void foo(bool isThrow)
{
auto_ptr
//TC *pTC = new TC; //方法1
try
{
if(isThrow)
throw "haha";
}
catch(const char* e)
{
//delete pTC; //方法1
throw;
}
//delete pTC; //方法1
}
int main()
{
try
{
foo(true);
}
catch(...)
{
cout<<"caught"<
system("pause");
}
1.如果采用方案1,那么必須考慮到函數(shù)在因throw異常的時候釋放所分配的內存。 這樣造成的結果是在每個分支處都要很小心的手動 delete pTC;
2.如果采用方案2,那就無需操心何時釋放內存,不管foo()因何原因退出, 棧上對象pTC的析構函數(shù)都將調用,因此托管在之中的指針所指的內存必然安全釋放。
至此,智能指針的優(yōu)點已經(jīng)很明了了。
但是要注意使用中的一個陷阱,那就是指針的托管權是會轉移的。 例如在上例中,如果 auto_ptr
要避免這個問題,可以考慮使用采用了引用計數(shù)的智能指針,例如boost::shared_ptr等
auto_ptr不會降低程序的效率,但auto_ptr不適用于數(shù)組,auto_ptr根本不可以大規(guī)模使用。 shared_ptr也要配合weaked_ptr,否則會很容易觸發(fā)循環(huán)引用而永遠無法回收內存。理論上,合理使用容器加智能指針,C++可以完全避免內存泄露,效率只有微不足道的下降。

