1.Do you Smart Pointer?
????? Smart Pointer,中文名:智能指針, 舶來品?
????? 不可否認,資源泄露(resource leak)曾經是C++程序的一大噩夢.垃圾回收
????? 機制(Garbage Collection)一時頗受注目.然而垃圾自動回收機制并不能
????? 滿足內存管理的即時性和可視性,往往使高傲的程序設計者感到不自在.
????? 況且,C++實現沒有引入這種機制.在探索中,C++程序員創造了鋒利的
????? "Smart Pointer".一定程度上,解決了資源泄露問題.
????? 也許,經常的,你會寫這樣的代碼:
????? //x擬為class:
//??class?x{
??????//????????????public:????????
??????//???????????????????int?m_Idata;
??????//????????????public:
??????//???????????????????x(int?m_PARAMin):m_Idata(m_PARAMin){}
??????//???????????????????void?print(){?cout<<m_Idata<<endl;?}
??????//????????????.....
??????//????????????}
??????//

??????void?fook()...{
??????x*?m_PTRx?=?new?A(m_PARAMin);
??????m_PTRx->DoSomething();?????//#2
??????delete?m_PTRx;
??????}

??????????? 是的,這里可能沒什么問題.可在復雜、N行、m_PTRclassobj所指對象生命周
????? 期要求較長的情況下,你能保證你不會忘記delete m_PTRclassobj嗎?生活中,
????? 我們往往不應該有太多的口頭保證,我們需要做些真正有用的東西.還有一個
????? 更敏感的問題:異常.假如在#2方法執行期異常發生,函數執行終止,那么new
????? 出的對象就會泄露.于是,你可能會說:那么就捕獲異常來保證安全性好了.
????? 你寫這樣的程式:

????void?fook()...{
??????A*?m_PTRx?=?new?x(m_PARAMin);

??????try...{
??????????m_PTRx->DoSomething();
??????}

??????catch(..)...{
??????????delete?m_PTRx;
??????????throw;
??????}
??????delete?m_PTRx;
??????}

????? 哦!天哪!想象一下,你的系統,是否會象專為捕獲異常而設計的.
????? 一天,有人給你建議:"用Smart Pointer,那很安全.".你可以這樣重寫你的程序:
???

??????void?fook()...{
??????auto_ptr<x>?m_SMPTRx(new?x(m_PARAMin));
??????m_SMPTRx->DoSomething();
??????}?
????? OK!你不太相信.不用delete嗎?
????? 是的.不用整天提心吊膽的問自己:"我全部delete了嗎?",而且比你的delete
????? 策略更安全.
????? 然后,還有人告訴你,可以這樣用呢:
?
?ok1.
??????auto_ptr<x>?m_SMPTR1(new?x(m_PARAMin));?
??????auto_ptr<x>?m_SMPTR2(m_SMPTR1);??//#2
??????May?be?you?can?code?#2?like?this?:
??????????auto_ptr<x>?m_SMPTR2;
??????????m_SMPTR2?=?m_SMPTR1;??????
??????ok2.
??????auto_ptr<int>?m_SMPTR1(new?int(32));
??????
??????ok3.
??????auto_ptr<int>?m_SMPTR1;
??????m_SMPTR1?=?auto_ptr<int>(new?int(100));
??????也可以:
??????auto_ptr<int>?m_SMPTR1(auto_ptr<int>(new?int(100)));
??????
??????ok4.
??????auto_ptr<x>?m_SMPTR1(new?x(m_PARAMin));
??????m_SMPTR1.reset(new?x(m_PARAMin1));
??????
??????ok5.
??????auto_ptr<x>?m_SMPTR1(new?x(m_PARAMin));
??????auto_ptr<x>?m_SMPTR2(m_SMPTR.release());
??????cout<<(*m_SMPTR2).m_Idata<<endl;??
??????
??????ok6.

??????auto_ptr<int>?fook()...{
??????return?auto<int>(new?int(100));
??????}
?

?????????? ok7.............and so on
?????
????? 但不可這樣用:
??????
??????
no1.???
??????char*?chrarray?=?new?char[100];
??????strcpy(chrarray,"I?am?programming.");
??????auto_ptr<char*>?m_SMPTRchrptr(chrarray);
??????//auto_ptr并不可幫你管理數組資源?????
???????
??????no2.
??????vector<auto_ptr<x>>?m_VECsmptr;
??????m_VECsmptr.push_back(auto_ptr<int>(new?int(100)));
??????//auto_ptr并不適合STL內容.
???????
??????no3.
??????const?auto_ptr<x>?m_SMPTR1(new?x(100));
??????auto_ptr<x>?m_SMPTR(new?x(200));
??????
??????no4.
??????x?m_OBJx(300);
??????auto_ptr<x>?m_SMPTR(&m_OBJx);
??????
??????no5
??????x*?m_PTR?=?new?x(100);
??????auto_ptr<x>?m_SMPTR?=?m_pTR;
??????
????? no6..........and so on
????? 預先提及所有權的問題,以便下面帶著疑問剖析代碼?
?
??????power1.
??????auto_ptr<x>?m_SMPTR1(new?x(100));
??????auto_ptr<x>?m_SMPTR2?=?m_SMPTR1;
??????m_SMPTR2->print();
??????//輸出:100.
??????m_SMPTR1->print();
??????//!!?非法的.

??????power2.
??????auto_ptr<x>?m_SMPTR(new?x(100));
??????

??????auto_ptr<x>?returnfun(auto_ptr<x>?m_SMPTRin)...{
??????return?m_SMPTRin;
??????}
??????
??????auto_ptr<x>?=?returnfun(m_SMPTR);??//#5

?
????? //在上面的#5中,我要告訴你對象所有權轉移了兩次.
????? //什么叫對象所有權呢?
??
??? 2. std::auto_ptr的設計原理
??????
????? 上面的一片正確用法,它們在干些什么?
??????????? 一片非法,它們犯了什么罪?
??????????? 一片什么所有權轉移,它的內部機智是什么?
????? 哦!一頭霧水?下面我們就來剖析其實現機制.
????? 基礎知識:
????????????? a.智能指針的關鍵技術:在于構造棧上對象的生命期控制
??????????????? 堆上構造的對象的生命期.因為在智能指針的內部,存儲
??????????????? 著堆對象的指針,而且在構析函數中調用delete行為.
??????????????? 大致機構如下:
??????????????? x* m_PTRx = new x(100);//#1
??????????????? template<typename T>
??????????????? auto_ptr{
??????????????? private:
??????????????? T* m_PTR;//維護指向堆對象的指針,在auto_ptr定位后????
??????????????? ....???? //它應該指向#1構造的對象,即擁有所有權.
??????????????? ~auto(){ delete m_PTR; }
??????????????? ....
??????????????? }
???????????? b.所有權轉移之說
?????????????? 上面曾有一非法的程式片段如下:
?????????????? auto_ptr<x> m_SMPTR1(new x(100));
?????????????? auto_ptr<x> m_SMPTR2 = m_SMPTR1;
?????????????? m_SMPTR2->print();
?????????????? //輸出:100.
?????????????? m_SMPTR1->print();
?????????????? //!! 非法的.
?????????????? 按常理來說,m_SMPTR->print();怎么是非法的呢?
?????????????? 那是因為本來,m_SMPTR1維護指向new x(100)的指針,
?????????????? 可是m_SMPTR2 = m_SMPTR1;auto_ptr內部機制使得m_SMPTR1將對象的地址
?????????????? 傳給m_SMPTR2,而將自己的對象指針置為0.
?????????????? 那么自然m_SMPTR->print();失敗.
?????????????? 這里程序設計者要負明顯的職責的.
?????????????? 那么auto_ptr為什么采取這樣的策略:保證所有權的單一性.
?????????????????????????????????????????????? 亦保證了系統安全性.
?????????????? 如果多個有全權的auto_ptr維護一個對象,那么在你消除一個
?????????????? auto_ptr時,將導致多個auto_ptr的潛在危險.
?????
?????? 下面我們以SGI-STL的auto_ptr設計為樣本(去掉了無關分析的宏),來剖析其原理.
?

#1??template?<class?_Tp>?class?auto_ptr?...{
???????#2??private:
???????#3??_Tp*?_M_ptr;??//定義將維護堆對象的指針

???????#4??public:
???????#5??typedef?_Tp?element_type;??//相關類型定義

???????#6??explicit?auto_ptr(_Tp*?__p?=?0)?__STL_NOTHROW?:?_M_ptr(__p)?...{}

???????#7??auto_ptr(auto_ptr&?__a)?__STL_NOTHROW?:?_M_ptr(__a.release())?...{}
???????#8??template?<class?_Tp1>?auto_ptr(auto_ptr<_Tp1>&?__a)?__STL_NOTHROW

?????????????????????????????????????????????????:?_M_ptr(__a.release())?...{}
???????????//#6、#7、#8是auto_ptr構造函數的三個版本.
???????????//#6注釋:傳入對象的指針,構造auto_ptr.explicit關鍵字:禁止隱式轉換.
???????????//????????這就是ok2正確,而no5(隱式轉換)錯誤的原因.
???????????//#7注釋:拷貝構造函數.
???????????//????????傳入auto_ptr實例,構造auto_ptr.?ok1、ok3使用了這個構造式.
???????????//????????它是一個很關鍵的構造函數,在具體情況下,我們再分析
???????????//#8注釋:auto_ptr的模板成員,可在繼承對象重載的基礎上,實現特殊功能.
???????????//???
???????????//???舉例:
???????????//???class?A{?public:?
???????????//??????????virtual?void?fook(){cout<<"I?am?programming"<<endl;
???????????//??????????/*..........*/???????????????????????????????????};?
???????????//???class?B?:?public?A?{
???????????//??????????virtual?void?fook(){?cout<<"I?am?working"<<endl;
???????????//?????????/*...........*/??????????????????????????????????};??
???????????//???auto_ptr<A>?m_SMPTRa(new?A(33));//實質:
???????????//???auto_ptr<B>?m_SMPTRb(m_SMPTRa);?//基類的指針可以賦給派生類的指針??????????
???????????//??????????????
???????????//???auto_ptr<B>?m_SMPTRb(new?B(44));//實質:
???????????//???auto_ptr<A>?m_SMPTRa(m_SMPTRb);?//派生類的指針不可賦給基類的指針
???????????//???????
???????????//???auto_ptr<A>?m_SMPTRa(new?B(33));??//?ok!??
???????????//???m_SMPTRa->fook()將調用派生類B的fook()
???????????//???m_SMPTRa->A::fook()將調用基類A的fook()
???????????//????
???????????//???auto_ptr<B>?m_SMPTRb(new?A(33));??//?wrong!
???????????//???
???????????//???

???????#9??auto_ptr&?operator=(auto_ptr&?__a)?__STL_NOTHROW?...{

???????#10?if?(&__a?!=?this)?...{?delete?_M_ptr;??_M_ptr?=?__a.release();?}
???????#11?return?*this;
???????#12?}
?????????
???????#13?template?<class?_Tp1>

???????#14?auto_ptr&?operator=(auto_ptr<_Tp1>&?__a)?__STL_NOTHROW?...{

???????#15?if?(__a.get()?!=?this->get())?...{?delete?_M_ptr;?_M_ptr?=?__a.release();?}
???????#16?return?*this;
???????#16?}??
??????????//
??????????//?#9~~#16?兩個版本的指派函數.
??????????//?????????delete?_M_ptr;?在指派前,銷毀原維護的對象.
??????????//?????????_a.release()?;?release操作,詳細代碼參見#20~~#23.
??????????//????????????????????????用于*this獲得被指派對象,
??????????//????????????????????????且將原維護auto_ptr置空.
??????????//?????no3使用了第一種指派.
??????????//?????而權限轉移正是_a.release()的結果.
??????????

???????#17?~auto_ptr()?__STL_NOTHROW?...{?delete?_M_ptr;?}
??????????//構析函數.消除對象.注意這里對對象的要求!
??????????

???????#17?_Tp&?operator*()?const?__STL_NOTHROW?...{??return?*_M_ptr;?}

???????#18?_Tp*?operator->()?const?__STL_NOTHROW?...{?return?_M_ptr;??}

???????#19?_Tp*?get()?const?__STL_NOTHROW?...{?return?_M_ptr;?}
?????????//
?????????//??操作符重載.
?????????//?#17注釋:提領操作(dereference),獲得對象.?見ok5用法.
?????????//?#18注釋:成員運算符重載,返回對象指針.
?????????//?#19注釋:普通成員函數.作用同于重載->運算符
?????????//

???????#20?_Tp*?release()?__STL_NOTHROW?...{
???????#21?_Tp*?__tmp?=?_M_ptr;
???????#22?_M_ptr?=?0;
???????#23?return?__tmp;????????????????}
?????????//上面已經詳解??????
?

???????#24?void?reset(_Tp*?__p?=?0)?__STL_NOTHROW?...{
???????#25?delete?_M_ptr;
???????#26?_M_ptr?=?__p;??????????????????????????}
?????????//
?????????//傳入對象指針,改變auto_ptr維護的對象
?????????//???????且迫使auto_ptr消除原來維護的對象
?????????//???????見ok3用法.

?????????//?According?to?the?C++?standard,?these?conversions?are?required.??Most
?????????//?present-day?compilers,?however,?do?not?enforce?that?requirement---and,?
?????????//?in?fact,?most?present-day?compilers?do?not?support?the?language?
?????????//?features?that?these?conversions?rely?on.
?????????


??????????????? //下面這片段用于類型轉化,目前沒有任何編譯器支持
???????? //具體技術細節不訴.?????????
?
?????????
#ifdef?__SGI_STL_USE_AUTO_PTR_CONVERSIONS

??????#27?private:
??????#28?template<class?_Tp1>?

??????#29?struct?auto_ptr_ref?...{?_Tp1*?_M_ptr;?auto_ptr_ref(_Tp1*?__p)?:?_M_ptr(__p)?...{}
?????????????????????????????};

??????#30?public:
??????#31?auto_ptr(auto_ptr_ref<_Tp>?__ref)?__STL_NOTHROW

???????????????????????????????:?_M_ptr(__ref._M_ptr)?...{}
??????#32?template?<class?_Tp1>?
??????#33?operator?auto_ptr_ref<_Tp1>()?__STL_NOTHROW?

??????#34?...{?return?auto_ptr_ref<_Tp>(this->release());?}
??????#35?template?<class?_Tp1>?operator?auto_ptr<_Tp1>()?__STL_NOTHROW

??????#36?...{?return?auto_ptr<_Tp1>(this->release());?}
??????#37?#endif?/*?__SGI_STL_USE_AUTO_PTR_CONVERSIONS?*/
??????#38?};


??????
????? OK!就是這樣了.
????? 正如上面原理介紹處敘說,
????? 你需要正視兩大特性:
????? 1.構造棧對象的生命期控制堆上構造的對象的生命期
????? 2.通過release來保證auto_ptr對對象的獨權.
?????
???? 在我們對源碼分析的基礎上,重點看看:
???? no系列錯誤在何處?
???? no1.
???????? 我們看到構析函數template<class _Tp>
???????????????????????? ~auto_ptr() _STL_NOTHROW
??????????????????????? { delete _M_ptr; }
???????? 所以它不能維護數組,
???????? 維護數組需要操作:delete[] _M_ptr;
???? no2.
??????? 先提部分vector和auto_ptr代碼:
??????? a.提auto_ptr代碼
??????????
????????

auto_ptr(auto_ptr&?__a)?__STL_NOTHROW?:?_M_ptr(__a.release())?...{}

????????
??????? b.提vector代碼
?????????

??????????Part1:

??????????void?push_back(const?_Tp&?__x)?...{

??????????if?(_M_finish?!=?_M_end_of_storage)?...{
??????????construct(_M_finish,?__x);
??????????++_M_finish;
??????????}
??????????else
?????????_M_insert_aux(end(),?__x);
??????????}
????????
?????????Part2:
?????????template?<class?_T1,?class?_T2>
?????????inline?void?construct(_T1*?__p,

?????????//++++++++++++++++++++++++++++++++?
?????????//?????????const?_T2&?__value)?{?+
?????????//++++++++++++++++++++++++++++++++
?????????//??new?(__p)?_T1(__value);??????+
?????????//++++++++++++++++++++++++++++++++

?????????}
?????????
?????????Part3.
?????????template?<class?_Tp,?class?_Alloc>
?????????void?
?????????vector<_Tp,?_Alloc>::_M_insert_aux
?????????(iterator?__position,

??????????//++++++++++++++++++++++++++++++++?
??????????//????????const?_Tp&?__x)???????++
??????????//++++++++++++++++++++++++++++++++???
?

?????????...{

?????????if?(_M_finish?!=?_M_end_of_storage)?...{
?????????construct(_M_finish,?*(_M_finish?-?1));
?????????++_M_finish;

?????????//++++++++++++++++++++++++++++++++
?????????//?????_Tp?__x_copy?=?__x;???????+
?????????//++++++++++++++++++++++++++++++++

?????????copy_backward(__position,?_M_finish?-?2,?_M_finish?-?1);
?????????*__position?=?__x_copy;
?????????}

?????????else?...{
?????????const?size_type?__old_size?=?size();
?????????const?size_type?__len?=?__old_size?!=?0???2?*?__old_size?:?1;
?????????iterator?__new_start?=?_M_allocate(__len);
?????????iterator?__new_finish?=?__new_start;

?????????__STL_TRY?...{
?????????__new_finish?=?uninitialized_copy
?????????(_M_start,?__position,?__new_start);
?????????construct(__new_finish,?__x);
?????????++__new_finish;
?????????__new_finish?=?uninitialized_copy
????????(__position,?_M_finish,?__new_finish);
????????}
????????__STL_UNWIND((destroy(__new_start,__new_finish),?
??????????????????_M_deallocate(__new_start,__len)));
???????destroy(begin(),?end());
???????_M_deallocate(_M_start,?_M_end_of_storage?-?_M_start);
???????_M_start?=?__new_start;
???????_M_finish?=?__new_finish;
???????_M_end_of_storage?=?__new_start?+?__len;
???????}
???????}

?
?????? 從提取的vector代碼,Part1可看出,push_back的操作行為.
?????? 兵分兩路,可是再向下看,你會發現,無一例外,都
?????? 通過const _Tp& 進行拷貝行為,那么從auto_ptr提出的片段就
?????? 派上用場了.
?????? 可你知道的,auto_ptr總是堅持對對象的獨權.那必須修改
?????? 原來維護的對象,而vector行為要求const _Tp&,這樣自然會產生
?????? 問題.一般編譯器是可以發覺這種錯誤的.
?????? 其實,STL所有的容器類都采用const _Tp&策略.
?
?????? //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
????? + 看了sutter和Josuttis的兩篇文章中,都提及:??????????????????? +
????? + STL容器不支持auto_ptr原因在于copy的對象只是獲得所有權的對象, +
????? + 這種對象不符合STL的要求.可是本人總感覺即時不是真正的復制對象,+
????? + 但我用vector<auto_ptr<x> >的目的就在于維護對象,并不在乎????? +
????? + 所謂的完全對象.而且我用自己寫的Smart Pointer配合STL容器工作, +
????? + 很正常.那需要注意的僅僅是const問題.????????????????????????? +
????? +????????????????????????????????????????????????????????????? +
????? //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
???? no3.
??????? 這個也是auto_ptr隱含的所有權問題引起的.
??????? const auto_ptr不允許修改.
??????? 隨便提及:const對象不代表對象一點不可以改變.
????????????????? 在兩種const語義下,都有方法修改對象或對象內部指針維護的對象
???????????????? 或其它資源.
???? no4.
??????? 再看auto_ptr的構析函數.
??????? delete不可以消除棧上資源.
???? no5.
??????? 依賴傳入對象指針的構造函數被聲明為explicit,禁止隱式轉換.
???
??? 3.auto_ptr高級使用指南
?????
????? a.類成員auto_ptr,禁止構造函數以構建"完全對象"
?
Programme1:

????????struct?Structx...{
???????????????int?m_Idata;
???????????????char?m_CHRdata;

???????????????/**//*?and?so?on?*/
????????};
????????出于對象編程的理念,
????????我們將Structx打造成包裹類:

????????class?StructWrapper...{
????????private:
????????Structx*?m_STRTxptr;
????????public:

????????StructWrapper():m_STRTxptr(new?Structx)...{}

????????~StructWrapper()...{delete?m_SMRTxptr;?}
????????public:

????????void?Soperator1()...{?/**//*?針對Structx對象的特性操作?*/}

????????void?Soperator2()...{?/**//*?針對Structx對象的特性操作?*/}????????

????????/**//*??and?so?on?*/
????????};?
????????
????????Programme2:

????????class?StructWrapper...{
????????private:
????????auto_ptr<Structx>?m_SMPTRx;
????????public:

????????StructWrapper():m_SMPTRAx(new?Structx)...{}
????????public:

????????void?Soperator1()...{?/**//*?針對Structx對象的特性操作?*/}

????????void?Soperator2()...{?/**//*?針對Structx對象的特性操作?*/}????????

????????/**//*??and?so?on?*/
????????};?
????????
????????Programme3:
????????StructWrapper::StructWrapper(const?StructWrapper&?other)

????????:?M_SMPTRx(new?Struct(*other.m_SMPTRx))?...{?}

????????StructWrapper&?StructWrapper::operator=(const?StructWrapper?&other)...{
????????*m_SMPTRx?=?*other.m_SMPTRx;
????????};

??????????????? 處于對構建于堆中的對象(new Structx)智能維護的需要.
??????? 我們將programme1改造為programme2:
??????? 不錯,對象是可以智能維護了.
??????? 對于包裹類(StructWrapper)你是否會有這樣的構造或指派操作:
???????? StructWrapper m_SMPTRWrapper2(m_SMPTRWrapper1);
??????
???????? StructWrapper mSMPTRWrapper2 = m_SMPTRWrapper1;
???????? 那么請注意:
???????? 當你坦然的來一個:M_SMPTRWrapper1->Soperator1();的時候,
???????? 系統崩潰了.
???????? 不必驚訝,所有權還是所有權問題.
???????? 問一下自己:當programme2默認拷貝構造函數作用時,又調用了auto_ptr的
???????? 默認構造函數,那么auto_ptr所有的默認行為都遵循獨權策略.對,就這樣.
???????? m_SMPTRWrapper1的對象所有權轉移給了m_SMPTRWrapper2.
???????? M_SMPTRWrapper1->Soperator1();那么操作變成了在NULL上的.
???????? 哦!系統不崩潰才怪.
???????? 那么你需要想,programme3那樣利用auto_ptr的提領操作符自己的
???????? 構造"完全對象".
?????? b.利用const關鍵字,防止不經意的權限轉移
????????
???????? 從上面的敘述,你可看出,所有權轉移到處可以釀成大禍.
???????? 而對于一般應用來說,獨權又是很好的安全性策略.
???????? 那么我們就用const來修飾auto_ptr,禁止不經意的錯誤.
???????
???????? 當然上面提及:并不代表auto_ptr是不可修改的.
???????? 處于需要,從兩種const語義,你都可實現修改.
???????? 然,你還希望在函數傳入傳出auto_ptr那么你可傳遞auto_ptr的引用,
???????? 那就萬無一失了: void fook(const auto_ptr<x>& m_PARAMin);
???????? 在返回后賦予其它時,使用引用是不行的.你得用指針.
???????? 因為引用無論作為lvalue還是rvaluev,都會調用構造或指派函數.
??? 4.你是否覺得std::auto_ptr還不夠完美
?????
????? 在實踐中,std::auto_ptr能滿足你的需求嗎???????????
?
????? Andrei Alexandrescu在一篇文章中,提及:有關Smart Pointer的技術就像
????? 巫術.Smart Pointer作為C++垃圾回收機制的核心,它必須足夠強大的、具有工業強度和安全性.
????? 但為了可一勞永逸我們還需要披荊斬棘繼續探索.
????? 下面在需求層面上,我們思索一下我們的智能指針還需要些什么?
?
??????? a. std::auto_ptr 能夠處理數組嗎?我們可以用智能指針來管理其它的資源嗎?
?????????? 譬如一個線程句柄、一個文件句柄 and so on !
??????? b. 對于我們的對象真的永遠實行獨權政策嗎?
??????? c. Our 智能指針還需要在繼承和虛擬層面上發揮威力 !
??????? d. 往往,需要擴展Our 智能指針的功能成員函數來滿足動態的需要 !
??????? e. 也許,你需要的還很多.
posted on 2006-12-15 17:35
清源游民 閱讀(7878)
評論(7) 編輯 收藏 引用 所屬分類:
C++