前面介紹了工廠模式控制了智能指針和資源對象的創建過程,現在介紹一下智能指針是如何利用代理模式來實現“類指針(like-pointer)”的功能,并控制資源對象的銷毀過程的。
2、代理模式
代理模式是為其它對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不直接引用另一個對象,而是通過一個代理對象在客戶端和目標對象之間起到中介的作用,這個代理對象提供了和目標對象完全一樣的功能接口,客戶端訪問這個代理對象就像是在訪問目標對象一樣,同時代理對象根據需要在訪問目標對象時進行了控制。智能指針就起到了客戶端和裸指針的中介代理作用。
按照GOF的說法,組成代理模式的角色有:
主題角色(Subject):定義了代理角色和真實角色的共用接口方法,這樣在任何使用真實角色的地方都有可以使用代理角色。
代理角色(Proxy):實現主題角色接口方法,是真實角色的代理,通過真實角色的業務邏輯方法來實現主題角色的接口方法,并可以附加自己的操作,對真實角色的行為進行控制。
真實角色(RealSubject):實現主題角色接口方法,定義了真實角色所要實現的業務邏輯,供代理角色調用。
代理模式的類結構如下圖所示:
智能指針使用面向對象技術封裝了C++中的裸指針,是裸指針的包裝類,它按照RAII慣用法來管理指針資源,可以在失去生存期時同時把所管理的資源釋放掉,得益于C++的操作符重載,智能指針提供了*、->等操作符,它的行為就像是指針,可以按照指針的形式來對智能指針對象進行操作,比如使用“*”操作符來解引用所指向的內容,使用“->”操作符來訪問所指向的結構對象的成員,就像是在操作一個普通指針一樣。
我們不妨腦洞大開一下,如果把“指針”當作是一種對象,使用面向對象進行設計的話,可以大體定義成類似下面的class:
template<typename T>
class pointer {
private:T *address; // 數據成員public:pointer(T *p);// 下面是成員函數T *operator++();T *operator--();T *operator+=(int offset);T *operator-=(int offset);T operator*();T operator[int index];T *operator->();delete();
}
再看unique_ptr和shared_ptr類,可以把它們當作是pointer基類的派生類,它們提供了pointer定義的成員方法,并且還包含了一個pointer指針。
以shared_ptr為例,可以視為下面的class:
class shared_ptr : public pointer {
private:pointer ptr; // 資源對象指針,指向被代理對象ref_count *refcnt; // 引用計數
public:shared_ptr(pointer p) {...其它操作ptr = p;}T operator*() {return *ptr;}T *operator->() {return ptr;}~shared_ptr() {...其它操作ptr->delete();}...其它成員函數
}
從類的結構形式上看,shared_ptr類繼承于pointer類,并且擁有一個pointer類型的數據成員,正好符合代理模式的類結構圖:基類pointer對應subject主題類,而shared_ptr類對應proxy代理類,shared_ptr還包含了一個類型為基類pointer的數據成員ptr,即被代理對象,在實踐中就是裸指針。再看shared_ptr的功能,它public繼承了pointer類,因此它對外提供了pointer類的接口,例如解引用操作符*和成員訪問操作符->,當調用shared_ptr的這些接口時,它會把請求轉發到數據成員ptr的對應接口上。同時它也對相關的接口做了控制,比如為了防止訪問內存越界,沒有提供指針的算術運算,更重要的是,它控制了資源對象的生存期管理,比如,只有在引用計數為0時才釋放資源對象,這也正是智能指針的重要功能。也就說,shared_ptr對內包含了一個指針類型成員,對外又表現為一個指針類型,而且還對指針成員的行為做出了控制。這不正是代理模式嗎?
由此可見,智能指針unique_ptr和shared_ptr的實現正是代理模式的典型應用。當然,如果嚴格地按照面向對象編程的特征,因為C++中沒有“指針”類,指針只是C++固有的基本類型,可能不是標準的代理模式,雖然外在形式上不同,但確實表現出了代理模式的思想和意圖:提供了與裸指針相同的功能,并把請求轉發給裸指針處理,同時對裸指針的操作進行了控制(如不提供某種操作,或者限制使用),最核心的控制是能夠對裸指針指向的資源對象進行了生存期管理。
此外,C++中還有另一種形式的“類指針”對象:迭代器,它也實現了*、->等操作符函數,而且還提供了++、–、+=、-=等算術操作符,對照代理模式來分析,它也是使用代理模式來實現的(注:它是標準的迭代器模式,這里關注的是它的“類指針”特性)。不過,智能指針作為指針的代理對象,控制的是被代理對象——指針的生存期管理,而迭代器作為“集合指針”(如指向數組的指針)的代理對象,控制的是對所代理的集合指針的遍歷訪問行為,可以不用關心底層的數據存儲方式,同時控制訪問時不會越界,顯然它的核心功能是指針的算術操作,因為容器里存放的都是對象集合,不止一個,為迭代器提供指針的算術運算操作再正常不過了。
總之,盡管指針在編程使用時,容易發生內存泄漏、指針越界、野指針、重復釋放等錯誤。但是通過它的代理對象-智能指針,可以很好的控制指針的行為,盡可能的避免了一些錯誤的發生。
如果說智能指針使用工廠模式控制了資源對象的創建過程,那它使用代理模式控制了資源對象的銷毀過程:可以控制它在智能對象失去生存期時也立即銷毀資源對象,如unique_ptr,也可以控制它在最后一個引用它的智能指針對象失去生存期時再銷毀,如shared_ptr。
智能指針又是怎樣釋放資源對象的呢?下一篇文章介紹所使用的另一種設計模式。