14.智能(smart)指標,實現細節

來源:互聯網
上載者:User

它本質上是一個對象,但是被設計成使用方法和外觀都和內建指標相似。

智能指標是從模板中產生的,模板的參數確定指向的物件類型。大部分智能指標的模板看起來可能會是這樣:

template<class T>class SmartPtr {public:SmartPtr(T* realPtr = 0); // 建立一個智能指標// 指向dumb pointer所指的對象。未初始化的指標,預設值為0(null)SmartPtr(const SmartPtr& rhs); // 拷貝一個智能指標~SmartPtr(); // 釋放智能指標// 賦值操作SmartPtr& operator=(const SmartPtr& rhs);T* operator->() const; // 解引用(通過->)一個智能指標// 以訪問所指對象的成員T& operator*() const; // 解引用(通過&)智能指標private:T *pointer; // 智能指標所指的對象};

1.智能指標的構造,賦值和析構
建構函式很簡單,把一個智能指標指向的對象賦值給內部的私人成員pointer

,否則把內部指標設為0.

2.對於拷貝建構函式,賦值操作符和解構函式,由於存在對所指向的對象所有權問題所以實現起來比較複雜。對於一個智能指標,當它被釋放時必須負責刪除它所指向的對象。假設智能指標指向的對象是動態分配的,這種假設是比較常見的。

看一下標準C++庫中的auto_ptr模板,它是一個指向堆對象的智能指標。在auto_ptr釋放時,它的解構函式負責刪除它所指向的對象。

template<class T>class auto_ptr {public:auto_ptr(T *ptr = 0): pointee(ptr) {}~auto_ptr() { delete pointee; }...private:T *pointee;};

假如auto_ptr擁有對象時,它可以正常運行。但是當auto_ptr被拷貝或被賦值時,會發生什麼情況呢?
auto_ptr<TreeNode> ptn1(new TreeNode);

auto_ptr<TreeNode> ptn2 = ptn1; // 調用拷貝建構函式

//會發生什麼情況?

auto_ptr<TreeNode> ptn3;

ptn3 = ptn2; // 調用 operator=;

// 會發生什麼情況?

如果只簡單拷貝內部指標的話,會導致兩個auto_ptr指向一個相同的對象。當釋放auto_ptr時會重複刪除兩次這個對象。這將導致災難性的錯誤。

另一種方法是通過調用new,建立一個所指對象的新拷貝。這確保了不會有許多指向同一個對象的auto_ptr,但是建立(以後還得釋放)新對象會造成不可接受的效能損耗。並且我們不知道要建立什麼類型的對象,因為auto_ptr<T>對象不用必須指向類型為T的對象,它也可以指向T的衍生類別型對象。虛擬建構函式可能協助我們解決這個問題,但是好象不能把它們用在auto_ptr這樣的通用類中。

如果quto_ptr禁止拷貝和賦值,就可以消除這個問題,但是採用“當auto_ptr被拷貝和賦值時,對象所有權隨之被傳遞”的方法,是一個更具靈活性的解決方案:

template<class T>class auto_ptr {public:...auto_ptr(auto_ptr<T>& rhs); // 拷貝建構函式auto_ptr<T>& // 賦值operator=(auto_ptr<T>& rhs); // 操作符...};template<class T>auto_ptr<T>::auto_ptr(auto_ptr<T>& rhs){pointee = rhs.pointee; // 把*pointee的所有權// 傳遞到 *thisrhs.pointee = 0; // rhs不再擁有} // 任何東西template<class T>auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs){if (this == &rhs) // 如果這個對象自我賦值return *this; // 什麼也不要做delete pointee; // 刪除現在擁有的對象pointee = rhs.pointee; // 把*pointee的所有權rhs.pointee = 0; // 從 rhs 傳遞到 *thisreturn *this;}

賦值操作符在接受新對象的所有權之前必須刪除原來擁有的對象。不然,它原來擁有的對象將永遠不會刪除,這將導致記憶體泄露。除了auto_ptr以外,沒人可以擁有它指向的對象。
因為調用auto_ptr的拷貝建構函式後,它的所有權被傳遞出去,它本身就不可用了。那麼在函數調用中,如果通過傳值的方式傳遞一個auto_ptr對象,這將產生嚴重的後果。因為在函數調用結束後,這個對象將無法繼續使用。

//將auto_ptr以傳值的方式傳遞void printTreeNode(ostream& s, auto_ptr<TreeNode> p){ s << *p; }int main(){auto_ptr<TreeNode> ptn(new TreeNode);...printTreeNode(cout, ptn); //通過傳值方式傳遞auto_ptr...}

當形參p被初始化時,調用ptn的拷貝建構函式,它所指向對象的所有權傳遞給p。在函數調用結束後,p離開了範圍,它的解構函式刪除它所指向的對象。但是這時ptn已經不再指向任何對象。所以在函數調用後對它的操作都將是未知的。可能會引發災難性的後果。程式崩潰。
通過const引用傳遞參數可以避免這一問題

void printTreeNode(ostream& s,const auto_ptr<TreeNode>& p){ s << *p; }智能指標的解構函式通常是這樣的:template<class T>SmartPtr<T>::~SmartPtr(){if (*this owns *pointee) {delete pointee;}}

有時刪除前不需要進行測試,例如在一個auto_ptr總是擁有它指向的對象時。而在另一些時候,測試會更為複雜:一個使用了引用計數智能指標必須在判斷是否有權刪除所指對象前調整引用計數值。當然還有一些智能指標象一般指標一樣,當它們被刪除時,對所指對象沒有任何影響。
3.實現解引用操作符

讓我們把注意力轉向智能指標的核心部分,operator*和operator-> 函數。前者返回所指的對象。理論上,這很簡單:

template<class T>T& SmartPtr<T>::operator*() const{perform "smart pointer" processing;return *pointee;}

這裡傳回型別必須是一個引用。如果返回對象,會導致災難性後果,這個問題前面已經討論過了。還有一個方面是,返回對象,你的智能指標將喪失對虛函數的支援。想一下,如果智能指標指向的類型不是T,而是T的派生對象(很明顯這是可以的),那麼當返回對象時,由於傳回型別是T,會發生對象切割問題。衍生類別的部分會丟失。當你調用對象的虛函數時,將不會觸發與原先所指向對象的動態類型相符的函數。
實現operator->:

template<class T>T* SmartPtr<T>::operator->() const{perform "smart pointer" processing;return pointee;//返回auto_ptr指向的原始指標}

4.測試智能指標是否為NULL
到目前為止,我們還無法測試一個智能指標是否為空白(實際上是判斷它指向的對象是否為空白)

SmartPtr<TreeNode> ptn;

...

if (ptn == 0) ... // error!

if (ptn) ... // error!

if (!ptn) ... // error!

雖然給智能指標加一個成員函數isNull來判斷是否為空白,是一件極其容易的事,但是,這將破壞它與一般指標使用上的相似性。因為我們無法想測試一般指標那樣測試智能指標。必須通過調用isNUll。另一種做法是提供隱式轉換操作符。一般應用於這種目的的類型轉換時void*:

template<class T>class SmartPtr {public:...operator void*(); // 如果智能指標為null,... // 返回0, 否則返回}; // 非0。SmartPtr<TreeNode> ptn;...if (ptn == 0) ... // 現在正確if (ptn) ... // 也正確if (!ptn) ... // 正確

象所有的類型轉換函式一樣,它有一個缺點:在一些情況下雖然大多數程式員希望它調用失敗,但是函數確實能夠成功地被調用(參見條款M5)。特別是它允許智能指標與完全不同的類型之間進行比較:
SmartPtr<Apple> pa;

SmartPtr<Orange> po;

...

if (pa == po) ... // 這能夠被成功編譯!

即使在SmartPtr<Apple> 和 SmartPtr<Orange>之間沒有operator= 函數,也能夠編譯,因為智能指標被隱式地轉換為void*指標,而對於內建指標類型有內建的比較函數。這種進行隱式類型轉換的行為特性很危險。

在void*類型轉換方面,也有一些變化。有些設計者採用到const void*的類型轉換,還有一些採取轉換到bool的方法。這些變化都沒有消除混合類型比較的問題。

有一種兩全之策可以提供合理的測試null值的文法形式,同時把不同類型的智能指標之間進行比較的可能性降到最低。這就是在智能指標類中重載operator!,若且唯若智能指標是一個null 指標時,operator!返回true:

這僅僅是智能指標的原理和細節的一小部分

下面是一個具體的auto_ptr的實現執行個體

template<class T>class auto_ptr {public:explicit auto_ptr(T *p = 0); //explicit防止建構函式支援隱式轉換template<class U> // 拷貝建構函式成員模板auto_ptr(auto_ptr<U>& rhs);//用另一個類型相容的auto_ptr對象初始化一個新的auto_ptr對象~auto_ptr();template<class U> // 賦值操作成員模板auto_ptr<T>& operator=(auto_ptr<U>& rhs); // 用另一個類型相容的auto_ptr對象給它賦值T& operator*() const; T* operator->() const; T* get() const; // 返回包容指標的當前值T* release(); // 放棄包容指標的所有權,並返回其當前值void reset(T *p = 0); // 刪除包容指標,獲得指標p的所有權private:T *pointee;template<class U> // 讓所有的auto_ptr類 成為友元friend class auto_ptr<U>; };template<class T>inline auto_ptr<T>::auto_ptr(T *p): pointee(p){}template<class T>inline auto_ptr<T>::auto_ptr(auto_ptr<U>& rhs): pointee(rhs.release()){}template<class T>inline auto_ptr<T>::~auto_ptr(){ delete pointee; }template<class T>template<class U>inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<U>& rhs){if (this != &rhs) reset(rhs.release());return *this;}template<class T>inline T& auto_ptr<T>::operator*() const{ return *pointee; }template<class T>inline T* auto_ptr<T>::operator->() const{ return pointee; }template<class T>inline T* auto_ptr<T>::get() const{ return pointee; }template<class T>inline T* auto_ptr<T>::release(){T *oldPointee = pointee;pointee = 0;return oldPointee;}template<class T>inline void auto_ptr<T>::reset(T *p){if (pointee != p) {delete pointee;pointee = p;}}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.