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

字號(hào):

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