C++基礎(chǔ)(auto_ptr作用和用法)

字號:

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