C++深度探索系列:智慧指標(Smart Pointer) [二]
深度探索智慧指標(Smart Pointer)
主題索引:
一、剖析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++條件,尋找構造更強大的智慧指標(SmartPointer)的策略
1.支援引用記數的多種設計策略
你聽說過COM和它著名的IUnknown介面吧?
IUnknown是幹什麼的?我要告訴你,IUnknown介面三個函式簽名中,
兩個是用來管理物件(CoClass Object,元件類物件)的記數來控制
它的生命週期的.
在實踐中,我們的物件並不是只用一次,只允許一個引用的.
那麼,誰來管理它的生命週期呢?
我們的策略是:引用記數. 當物件的引用記數為零時,就銷燬物件.
在沒有託管環境的情況下,事實上,銷燬物件的往往還是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中,
它或者同等語義的行為可以執行?
不可以.為什麼?
它本質上,相當與這樣的操作:
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.
關於第二種方法,這裡不再詳細敘說.
它涉及太多的細節,峰迴路轉的很難說清.
大體上,它是利用引用記數物件中維護的物件指標為void*
而在具體的呼叫是通過static_cast或reinterpret_cast轉化.
總之,所謂的SubClassing技術離不開轉化.
4.支援多執行緒條件下,執行緒安全的多種設計策略
對於標準C++,多執行緒問題並不很受關注.
原因在於目前,標準庫並不支援多執行緒.
策略1:
首先我們想到:對資料進行訪問同步.
那麼,我們有兩種方案:
a. 建立一個臨界區物件.將物件的執行傳遞給臨界區物件.
以保證安全.
b.利用臨時物件來完成任務,將臨界的責任留給被作用物件.
下面分析第二種的做法:
programme1:
class Widget
{
...
void Lock(); //進入臨界區
void Unlock(); //退出臨界區
};
programme2:
template <class T>
class LockingProxy
{
public:
LockingProxy(T* pObj) : pointee_ (pObj)
{ pointee_->Lock(); }
// 在臨時物件構造是就鎖定
// weight物件(臨界區).
~LockingProxy() { pointee_->Unlock(); }
//
// 在臨時物件銷燬時,退出臨界區.
//
T* operator->() const
{ return pointee_; }
//
// 這裡過載->運算子.將對臨時物件的方法執行
// 請求轉交給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
下面,我們模擬一下,執行的過程.
##1執行時,構建了臨時物件LockingProxy<T>(pointee_)
此物件在構造期間就鎖定Weight物件,並將DoSomethin()
方法傳遞給weight物件執行,在方法執行完,臨時物件消失,
構析函式退出臨界區.
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.事實上,你可以根據自己的需要.
過載更多或加入功能成員函式.
--------------------------------------------------------------
待續
三、Generic Programming基礎技術和Smart Pointer
1.回首處理資源中的Traits技術
2.回首多執行緒支援的設計
四、COM實現中,Smart Pointer設計原理
五、著名C++庫(標準和非標準)中的Smart Pointer現狀
--------------------------------------------------------------
--------------------------------------------------------------
鄭重宣告:
允許複製、修改、傳遞或其它行為
但不準用於任何商業用途.
寫於 20/3/2003
最後修改: 20/3/2003
By RedStar81
[email protected]
-------------------------------------------------------------