主題索引:
一、剖析C++標準庫智能指針(std::auto_ptr)
1.Do you Smart Pointer?
2.std::auto_ptr的設計原理
3.std::auto_ptr高級使用指南
4.你是否覺得std::auto_ptr還不夠完美?
二、C++條件,尋找構造更強大的智能指針(Smart Pointer)的
策略
1.支持引用記數的多種設計策略
2.支持處理多種資源
3.支持Subclassing
4.支持多線程條件下,線程安全的多種設計策略
5.其它多種特殊要求下,再構造
三、Generic Programming基礎技術和Smart Pointer
1.回首處理資源中的Traits技術
2.回首多線程支持的設計
四、COM實現中,Smart Pointer設計原理
五、著名C++庫(標準和非標準)中的Smart Pointer現狀
---------------------------------------------------------------------
一、剖析C++標準庫智能指針(std::auto_ptr)
1.Do you Smart Pointer?
Smart Pointer,中文名:智能指針, 舶來品?
不可否認,資源泄露(resource leak)曾經是C++程序的一大噩夢.垃圾回收
機制(Garbage Collection)一時頗受注目.然而垃圾自動回收機制并不能
滿足內存管理的即時性和可視性,往往使高傲的程序設計者感到不自在.
況且,C++實現沒有引入這種機制.在探索中,C++程序員創(chuàng)造了鋒利的
"Smart Pointer".一定程度上,解決了資源泄露問題.
也許,經常的,你會寫這樣的代碼:
//x擬為class:
// class x{
// public:
// int m_Idata;
// public:
// x(int m_PARAMin):m_Idata(m_PARAMin){}
// void print(){ cout< // .....
// }
//
void fook(){
x* m_PTRx = new A(m_PARAMin);
m_PTRx->DoSomething(); //#2
delete m_PTRx;
}
是的,這里可能沒什么問題.可在復雜、N行、m_PTRclassobj所指對象生命周
期要求較長的情況下,你能保證你不會忘記delete m_PTRclassobj嗎?生活中,
我們往往不應該有太多的口頭保證,我們需要做些真正有用的東西.還有一個
更敏感的問題:異常.假如在#2方法執(zhí)行期異常發(fā)生,函數執(zhí)行終止,那么new
出的對象就會泄露.于是,你可能會說:那么就捕獲異常來保證安全性好了.
你寫這樣的程式:
void fook(){
A* m_PTRx = new A(m_PARAMin);
try{
m_PTRx->DoSomething();
}
catch(..){
delete m_PTRx;
throw;
}
delete m_PTRx;
}
哦!天哪!想象一下,你的系統(tǒng),是否會象專為捕獲異常而設計的.
一天,有人給你建議:"用Smart Pointer,那很安全.".你可以這樣重寫你的程序:
void fook(){
auto_ptr m_SMPTRx(new x(m_PARAMin));
m_SMPTRx->DoSomething();
}
OK!你不太相信.不用delete嗎?
是的.不用整天提心吊膽的問自己:"我全部delete了嗎?",而且比你的delete
策略更安全.
然后,還有人告訴你,可以這樣用呢:
ok1.
auto_ptr m_SMPTR1(new x(m_PARAMin));
auto_ptr m_SMPTR2(m_SMPTR1); //#2
May be you can code #2 like this :
auto_ptr m_SMPTR2;
m_SMPTR2 = m_SMPTR1;
ok2.
auto_ptr m_SMPTR1(new int(32));
ok3.
auto_ptr m_SMPTR1;
m_SMPTR1 = auto_ptr(new int(100));
也可以:
auto_ptr m_SMPTR1(auto_ptr(new int(100)));
ok4.
auto_ptr m_SMPTR1(new x(m_PARAMin));
m_SMPTR1.reset(new x(m_PARAMin1));
ok5.
auto_ptr m_SMPTR1(new x(m_PARAMin));
auto_ptr m_SMPTR2(m_SMPTR.release());
cout<<(*m_SMPTR2).m_Idata<
ok6.
auto_ptr fook(){
return auto(new int(100));
}
ok7.............and so on
但不可這樣用:
no1.
char* chrarray = new char[100];
strcpy(chrarray,"I am programming.");
auto_ptr m_SMPTRchrptr(chrarray);
//auto_ptr并不可幫你管理數組資源
no2.
vector> m_VECsmptr;
m_VECsmptr.push_back(auto_ptr(new int(100)));
//auto_ptr并不適合STL內容.
no3.
const auto_ptr m_SMPTR1(new x(100));
auto_ptr m_SMPTR(new x(200));
no4.
x m_OBJx(300);
auto_ptr m_SMPTR(&m_OBJx);
no5
x* m_PTR = new x(100);
auto_ptr m_SMPTR = m_pTR;
no6..........and so on
預先提及所有權的問題,以便下面帶著疑問剖析代碼?
power1.
auto_ptr m_SMPTR1(new x(100));
auto_ptr m_SMPTR2 = m_SMPTR1;
m_SMPTR2->print();
//輸出:100.
m_SMPTR1->print();
//!! 非法的.
power2.
auto_ptr m_SMPTR(new x(100));
auto_ptr returnfun(auto_ptr m_SMPTRin){
return m_SMPTRin;
}
auto_ptr = returnfun(m_SMPTR); //#5
//在上面的#5中,我要告訴你對象所有權轉移了兩次.
//什么叫對象所有權呢?
2. std::auto_ptr的設計原理
上面的一片正確用法,它們在干些什么?
一片非法,它們犯了什么罪?
一片什么所有權轉移,它的內部機智是什么?
哦!一頭霧水?下面我們就來剖析其實現機制.
基礎知識:
a.智能指針的關鍵技術:在于構造棧上對象的生命期控制
堆上構造的對象的生命期.因為在智能指針的內部,存儲
著堆對象的指針,而且在構析函數中調用delete行為.
大致機構如下:
x* m_PTRx = new x(100);//#1
template
auto_ptr{
private:
T* m_PTR;//維護指向堆對象的指針,在auto_ptr定位后
.... //它應該指向#1構造的對象,即擁有所有權.
~auto(){ delete m_PTR; }
....
}
b.所有權轉移之說
上面曾有一非法的程式片段如下:
auto_ptr m_SMPTR1(new x(100));
auto_ptr 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為什么采取這樣的策略:保證所有權的單一性.
亦保證了系統(tǒng)安全性.
如果多個有全權的auto_ptr維護一個對象,那么在你消除一個
auto_ptr時,將導致多個auto_ptr的潛在危險.
下面我們以SGI-STL的auto_ptr設計為樣本(去掉了無關分析的宏),來剖析其原理.
#1 template 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 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"< // /*..........*/ };
// class B : public A {
// virtual void fook(){ cout<<"I am working"< // /*...........*/ };
// auto_ptr m_SMPTRa(new A(33));//實質:
// auto_ptr m_SMPTRb(m_SMPTRa); //基類的指針可以賦給派生類的指針
//
// auto_ptr m_SMPTRb(new B(44));//實質:
// auto_ptr m_SMPTRa(m_SMPTRb); //派生類的指針不可賦給基類的指針
//
// auto_ptr m_SMPTRa(new B(33)); // ok!
// m_SMPTRa->fook()將調用派生類B的fook()
// m_SMPTRa->A::fook()將調用基類A的fook()
//
// auto_ptr 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
#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.
//下面這片段用于類型轉化,目前沒有任何編譯器支持
//具體技術細節(jié)不訴.
#ifdef __SGI_STL_USE_AUTO_PTR_CONVERSIONS
#27 private:
#28 template
#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
#33 operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
#34 { return auto_ptr_ref<_Tp>(this->release()); }
#35 template 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
~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
inline void construct(_T1* __p,
//++++++++++++++++++++++++++++++++
// const _T2& __value) { +
//++++++++++++++++++++++++++++++++
// new (__p) _T1(__value); +
//++++++++++++++++++++++++++++++++
}
Part3.
template
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的操作行為.
兵分兩路,可是再向下看,你會發(fā)現,無一例外,都
通過const _Tp& 進行拷貝行為,那么從auto_ptr提出的片段就
派上用場了.
可你知道的,auto_ptr總是堅持對對象的獨權.那必須修改
原來維護的對象,而vector行為要求const _Tp&,這樣自然會產生
問題.一般編譯器是可以發(fā)覺這種錯誤的.
其實,STL所有的容器類都采用const _Tp&策略.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 看了sutter和Josuttis的兩篇文章中,都提及: +
+ STL容器不支持auto_ptr原因在于copy的對象只是獲得所有權的對象, +
+ 這種對象不符合STL的要求.可是本人總感覺即時不是真正的復制對象,+
+ 但我用vector >的目的就在于維護對象,并不在乎 +
+ 所謂的完全對象.而且我用自己寫的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 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();的時候,
系統(tǒng)崩潰了.
不必驚訝,所有權還是所有權問題.
問一下自己:當programme2默認拷貝構造函數作用時,又調用了auto_ptr的
默認構造函數,那么auto_ptr所有的默認行為都遵循獨權策略.對,就這樣.
m_SMPTRWrapper1的對象所有權轉移給了m_SMPTRWrapper2.
M_SMPTRWrapper1->Soperator1();那么操作變成了在NULL上的.
哦!系統(tǒng)不崩潰才怪.
那么你需要想,programme3那樣利用auto_ptr的提領操作符自己的
構造"完全對象".
b.利用const關鍵字,防止不經意的權限轉移
從上面的敘述,你可看出,所有權轉移到處可以釀成大禍.
而對于一般應用來說,獨權又是很好的安全性策略.
那么我們就用const來修飾auto_ptr,禁止不經意的錯誤.
當然上面提及:并不代表auto_ptr是不可修改的.
處于需要,從兩種const語義,你都可實現修改.
然,你還希望在函數傳入傳出auto_ptr那么你可傳遞auto_ptr的引用,
那就萬無一失了: void fook(const auto_ptr& m_PARAMin);
在返回后賦予其它時,使用引用是不行的.你得用指針.
因為引用無論作為lvalue還是rvaluev,都會調用構造或指派函數.
4.你是否覺得std::auto_ptr還不夠完美
在實踐中,std::auto_ptr能滿足你的需求嗎?
Andrei Alexandrescu在一篇文章中,提及:有關Smart Pointer的技術就像
巫術.Smart Pointer作為C++垃圾回收機制的核心,它必須足夠強大的、具有工業(yè)強度和安全性.
但為了可一勞永逸我們還需要披荊斬棘繼續(xù)探索.
下面在需求層面上,我們思索一下我們的智能指針還需要些什么?
a. std::auto_ptr 能夠處理數組嗎?我們可以用智能指針來管理其它的資源嗎?
譬如一個線程句柄、一個文件句柄 and so on !
b. 對于我們的對象真的永遠實行獨權政策嗎?
c. Our 智能指針還需要在繼承和虛擬層面上發(fā)揮威力 !
d. 往往,需要擴展Our 智能指針的功能成員函數來滿足動態(tài)的需要 !
e. 也許,你需要的還很多.
二、C++條件,尋找構造更強大的智能指針(SmartPointer)的策略
1.支持引用記數的多種設計策略
你聽說過COM和它著名的IUnknown接口吧?
IUnknown是干什么的?我要告訴你,IUnknown接口三個函數簽名中,
兩個是用來管理對象(CoClass Object,組件類對象)的記數來控制
它的生命周期的.
在實踐中,我們的對象并不是只用一次,只允許一個引用的.
那么,誰來管理它的生命周期呢?
我們的策略是:引用記數. 當對象的引用記數為零時,就銷毀對象.
在沒有托管環(huán)境的情況下,事實上,銷毀對象的往往還是auto_ptr.
而COM中,銷毀對象的是對象自己.
事實上,它和我們的智能指針不是一個級別上的概念.
我們的智能指針負責的是對象級的引用.而COM是以接口引用為
核心的.保證接口操作時,接口引用記數的自動管理.
哦!是的!那么我們怎樣給auto_ptr加上對象引用記數的功能?
策略1:
一個對象對應一個引用記數對象.
智能指針以記數對象為代理.
想象,這又歸到經典的"添加中間層"解決方案上了.
# 核心一:
我們添加一個 "引用記數class".
它的職責有二:
a.維護對象的引用記數.
b.維護對象的指針.
結構示意如下:
template<class T>
class ObjRefCounted{
private:
T* m_OBJ_Delegate_Ptr;
unsigned int m_UIcounted;
public:
explicit ObjRefCounted(T* m_Paramin = 0):
m_UIcounted(1), m_OBJ_Delegate_Ptr(m_Paramin){};
template<class M> ObjRefCounted(ObjRefCounted<M>& x) {
m_OBJ_Delegate_Ptr = x.m_OBJ_Delegate_Ptr); };
ObjRefCounted(const ObjRefCounted& x):m_UIcounted
(x.m_UIcounted), m_OBJ_Delegate_Ptr(x.m_ObjDelegate_Ptr){};
~ObjRefCounted();
void ReleaseRef ();
void AddRef ();
T* GetRealPointer () const;
};
# 核心二
在智能指針中維護一個引用記數class的指針
template<class T>
class SmartPointer{
public:
ObjRefCounted* _m_ObjRefCounted;
.....
.....
};
通過上面的兩個策略,我們就可以在智能指針構造時,為之付上一個
引用記數對象.這個對象負責托管Smart Pointer原本應該維護
的對象指針.并且負責最終消除對象.
在Smart Pointer中,我們將會涉及大量的_m_ObjRefCounted的操作.
下面簡敘一過程,詳細不訴,自己設計之.
譬如:當你將一個對象指針賦給Smart Pointer將構建一輔助的
引用記數托管對象,此時m_UIcounted為1,m_OBJ_Delegate_Ptr被賦
以對象指針,假如現在我又將Smart Pointer 賦給另一SmartPointer2
, 那么SmartPointer2調用_m_ObjRefCounted->ReleaseRef();
減少原來維護的對象的記數,將自己的_m_ObjRefCounted置為
SmartPointer2依附的記數對象,再調用_m_ObjRefCounted->AddRef();
OK!就是這樣的.
策略2.
在每一個智能指針內部維護一個對象指針和一個引用記數值的
的指針.
這里的重點在于維護一個引用記數值的指針,
它使得Smart Pointer之間保持一致的記數值成為可能.
結構示意如下:
template<class T>
class SmartPointer{
private:
T* m_ObjPtr;
unsigned int* RefCounted;
public:
explicit SmartPoint(T* PARAMin = 0) : m_ObjPtr(PARAMin),
RefCounted(new int(1)) { }
SmartPoint(const SmartPoint<T>& PARAMin2):
m_ObjPtr(PARAMin2.m_ObjPtr),
RefCounted(PARAMin2.RefCounted) { ++*RefCounted; }
....
...
};
不過這個方法的擴展性很差.
因為引用記數功能結合到Smart Pointer中去了.
一般不會用這種方法.
以上面的兩種策略為基礎,根據實際情況,可設計出更多的記數方法.
2.利用Traits(Partial Specialization)技術,
支持處理多種資源
在no1中,我們提到不可讓auto_ptr管理數組,那是因為
auto_ptr構析函數中調用的是delete的緣故.
數組不可,其它的如,文件句柄、線程句柄等當然更不可以了.
下面我們就這個問題來探討:
策略1.
通過函數指針來支持多種資源的處理.
我們的智能指針將設計成具有兩個參數的模板類.
第一個參數指示:資源的類型
第二個參數指示:處理資源的函數類型
結構示意如下:
typedef void FreeResourceFunction(void* p);
void DealSingleObject(void* p);
void DealArray(void* p);
void DealFile(void* p);
//
// 針對特殊的資源加入函數指針聲明
//
template<class Type , class DealFunction = DealSingleObject>
class SmartPointer{
public:
~SmartPointer(){ DealFunction(); }
...
...
/* Other codes */
};
inline void DealSingle(void* p)
{
if(p) delete p;
}
inline void DealArray(void* p){
if(p) delete[] p;
}
inline void DealFile(void* p){
if(p) p->close();
}
//
//針對特殊資源加入處理函數
//
oK!但是我們在使用這個策略的時候,一定要注意,
傳遞進的指針不能是錯誤的,這個你必須保證.
當然對上面的結構示意再改造,使之具有更強的
辨錯能力也是可取的.
3.支持Subclassing
關于智能指針中的Subclassing,是什么?
我們先來看一程式片段:
class BaseClass {};
class Derived : public BaseClass {};
auto_ptr<Derived> m_Derived;
auto_ptr<Base> m_Base;
auto_ptr<Derived> pDerived = new Derived;
m_Base = pDerived;
//
//m_Derived = (PDerived&)m_Base; //#1
//
看到上面的#1沒有,你認為在auto_ptr中,
它或者同等語義的行為可以執(zhí)行?
不可以.為什么?
它本質上,相當與這樣的操作:
BaseClass* m_BaseClass;
m_BaseClass = new DerivedClass(inParam);
這顯然是非法的.
在上面我們曾經,auto_ptr對具有虛擬特性的類,
也能體現出虛擬性.
然而那并不能訪問繼承的數據,實現的不是真正意義
上的SubClassing.
那么,我們這樣來實現這樣的功能.
策略1.
在上述引用記數部分敘述的SmartPoint中,我們作如下的操作:
template <class U> SmartPointer& operator = (const SmartPointer<U>& that)
{
if (m_pRep ! = reinterpret_cast<RefCountRep<T>* > (that.m_pRep))
{
ReleaseRef ();
m_pRep = reinterpret_cast<RefCountRep<T>* > (that.m_pRep);
AddRef ();
}
return *this;
}
};
不錯,reinterpret_cast,就是它幫我們解決了問題.
策略2.
關于第二種方法,這里不再詳細敘說.
它涉及太多的細節(jié),峰回路轉的很難說清.
大體上,它是利用引用記數對象中維護的對象指針為void*
而在具體的調用是通過static_cast或reinterpret_cast轉化.
總之,所謂的SubClassing技術離不開轉化.
4.支持多線程條件下,線程安全的多種設計策略
對于標準C++,多線程問題并不很受關注.
原因在于目前,標準庫并不支持多線程.
策略1:
首先我們想到:對數據進行訪問同步.
那么,我們有兩種方案:
a. 建立一個臨界區(qū)對象.將對象的執(zhí)行傳遞給臨界區(qū)對象.
以保證安全.
b.利用臨時對象來完成任務,將臨界的責任留給被作用對象.
下面分析第二種的做法:
programme1:
class Widget
{
...
void Lock(); //進入臨界區(qū)
void Unlock(); //退出臨界區(qū)
};
programme2:
template <class T>
class LockingProxy
{
public:
LockingProxy(T* pObj) : pointee_ (pObj)
{ pointee_->Lock(); }
// 在臨時對象構造是就鎖定
// weight對象(臨界區(qū)).
~LockingProxy() { pointee_->Unlock(); }
//
// 在臨時對象銷毀時,退出臨界區(qū).
//
T* operator->() const
{ return pointee_; }
//
// 這里重載->運算符.將對臨時對象的方法執(zhí)行
// 請求轉交給weight對象
//
private:
LockingProxy& operator=(const LockingProxy&);
T* pointee_;
};
programme3:
template <class T>
class SmartPtr
{
...
LockingProxy<T> operator->() const
{ return LockingProxy<T>(pointee_); }
//
// 核心就在這里:產生臨時對象
// LockingProxy<T>(pointee_)
private: sT* pointee_;
};
Programme4.
SmartPtr<Widget> sp = ...;
sp->DoSomething(); //##1
下面,我們模擬一下,執(zhí)行的過程.
##1執(zhí)行時,構建了臨時對象LockingProxy<T>(pointee_)
此對象在構造期間就鎖定Weight對象,并將DoSomethin()
方法傳遞給weight對象執(zhí)行,在方法執(zhí)行完,臨時對象消失,
構析函數退出臨界區(qū).
4.其它特殊要求下的再構造
a.回首當年,你是否覺的
auto_ptr<x> m_SMPTR = new x(100);
居然通不過.不爽!
No problem !
auto_ptr(T* m_PARAMin = 0) shrow() : m_Tp(m_PARAMin){}
解決問題.
b. Consider it:
void fook(x* m_PARAMin){};
可是我只有auto_ptr<x> m_SMPTR;
No problem !
T* operator T*(auto_ptr<T>& m_PARAMin) throw ()
{ return m_Tp; }
fook(m_SMPTR); // ok ! now
c.事實上,你可以根據自己的需要.
重載更多或加入功能成員函數.