程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++ primer智能指針(HasPtr)實現

C++ primer智能指針(HasPtr)實現

編輯:C++入門知識

智能指針顯然是C++吸引人的地方之一,必須掌握。看了《C++primer》,裡面著重講了智能指針的實現方式。


書中說到:

    “HasPtr(注:就是自定義的智能指針)在其它方面的行為與普通指針一致。具體而言,復制對象時,副本和原對象將指向同一基礎對象。如果通過一個副本改變基礎對象,則通過另一個對象訪問的值也會改變。

      新的HasPtr類需要一個析構函數來刪除指針。但是,析構函數不能無條件的刪除指針。”

     條件就是引用計數。如果該對象被兩個指針所指,那麼刪除其中一個指針,並不會調用該指針的析構函數,因為此時還有另外一個指針指向該對象。看來,智能指針主要是預防不當的析構行為,防止出現懸垂指針。

 

 
[cpp]
class U_Ptr { 
    friend class HasPtr; 
    int *ip; 
    size_t use; 
    U_Ptr(int *p) : 
        ip(p), use(1) { 
        cout << "U_ptr constructor called !" << endl; 
    } 
    ~U_Ptr() { 
        delete ip; 
        cout << "U_ptr distructor called !" << endl; 
    } 
}; 
裡面有個變量use和指針ip: use記錄了*ip對象被多少個HasPtr對象所指。假設現在又兩個HasPtr對象p1,p2指向了U_Ptr,那麼現在我delete  p1,use變量將自減1,  U_Ptr不會析構,那麼U_Ptr指向的對象也不會析構,那麼p2仍然指向了原來的對象,而不會變成一個懸空指針。當delete p2的時候,use變量將自減1,為0。此時,U_Ptr對象進行析構,那麼U_Ptr指向的對象也進行析構,保證不會內存洩露。  


包含指針的類需要特別注意復制控制,原因是復制指針時只復制指針中的地址,而不會復制指針指向的對象。

大多數C++類用三種方法之一管理指針成員

    (1)不管指針成員。復制時只復制指針,不復制指針指向的對象。當其中一個指針把其指向的對象的空間釋放後,其它指針都成了懸浮指針。這是一種極端

   (2)當復制的時候,即復制指針,也復制指針指向的對象。這樣可能造成空間的浪費。因為指針指向的對象的復制不一定是必要的。

   (3)第三種就是一種折中的方式。利用一個輔助類來管理指針的復制。原來的類中有一個指針指向輔助類,輔助類的數據成員是一個計數器和一個指針(指向原來的)(此為本次智能指針實現方式)。

     其實,智能指針的引用計數類似於java的垃圾回收機制:java的垃圾的判定很簡答,如果一個對象沒有引用所指,那麼該對象為垃圾。系統就可以回收了

 

[cpp]
# include  
using namespace std; 
class U_Ptr { 
    friend class HasPtr; 
    int *ip; 
    size_t use; 
    U_Ptr(int *p) : 
        ip(p), use(1) { 
        cout << "U_ptr constructor called !" << endl; 
    } 
    ~U_Ptr() { 
        delete ip; 
        cout << "U_ptr distructor called !" << endl; 
    } 
}; 
class HasPtr { 
public: 
    HasPtr(int *p, int i) : 
        ptr(new U_Ptr(p)), val(i) { 
        cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl; 
    } 
    HasPtr(const HasPtr& orig) : 
        ptr(orig.ptr), val(orig.val) { 
        ++ptr->use; 
        cout << "HasPtr copy constructor called ! " << "use = " << ptr->use 
                << endl; 
    } 
    HasPtr& operator=(const HasPtr&); 
    ~HasPtr() { 
        cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl; 
        if (--ptr->use == 0) 
            delete ptr; 
    } 
    int *get_ptr() const { 
        return ptr->ip; 
    } 
    int get_int() const { 
        return val; 
    } 
    void set_ptr(int *p) const { 
        ptr->ip = p; 
    } 
    void set_int(int i) { 
        val = i; 
    } 
    int get_ptr_val() const { 
        return *ptr->ip; 
    } 
    void set_ptr_val(int i) { 
        *ptr->ip = i; 
    } 
private: 
    U_Ptr *ptr; 
    int val; 
}; 
HasPtr& HasPtr::operator =(const HasPtr &rhs) {     //注意,這裡賦值操作符在減少做操作數的使用計數之前使rhs的使用技術加1,從而防止自我賦值 
    ++rhs.ptr->use; 
    if (--ptr->use == 0) 
        delete ptr; 
    ptr = rhs.ptr; 
    val = rhs.val; 
    return *this; 

int main() { 
    int *pi = new int(0); 
    HasPtr *hpa = new HasPtr(pi, 100); 
    HasPtr *hpb = new HasPtr(*hpa); 
    HasPtr *hpc = new HasPtr(*hpb); 
    HasPtr hpd = *hpa; 
 
    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
    hpc->set_ptr_val(10000); 
    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
    hpd.set_ptr_val(10); 
    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
    delete hpa; 
    delete hpb; 
    delete hpc; 
    cout << hpd.get_ptr_val() << endl; 
    return 0; 

這裡的賦值操作符比較麻煩,且讓我用圖表分析一番:

假設現在又兩個智能指針p1, p2,一個指向內容為42的內存,一個指向內容為100的內存,如下圖:

 

\


現在,我要做賦值操作,p2 = p1。對比著上面的

[cpp] view plaincopy HasPtr& HasPtr::operator =(const HasPtr &rhs) 
此時,rhs就是p1,首先將p1指向的ptr的use加1, [cpp] view plaincopy ++rhs.ptr->use; 
然後,做:


[cpp]
if (--ptr->use == 0) 
        delete ptr; 
因為,原先p2指向的對象現在p2不在指向,那麼該對象就少了一個指針去指,所以,use做自減1;


此時,條件成立。因為u2的use為1。那麼,運行U_Ptr的析構函數,而在U_Ptr的析構函數中,做了delete ip操作,所以釋放了內存,不會有內存洩露的問題。

接下來的操作很自然,無需多言:


[cpp]
ptr = rhs.ptr; 
val = rhs.val; 
return *this; 
做完賦值操作後,那麼就成為如下圖所示了。紅色標注的就是變化的部分:

 

\


而還要注意的是,重載賦值操作符的時候,一定要注意的是,檢查自我賦值的情況。

如圖所示:

 \
 


此時,做p1 = p1的操作。那麼,首先u1.use自增1,為2;然後,u1.use自減1,為1。那麼就不會執行delete操作,剩下的操作都可以順利進行。按《C++ primer》說法,“這個賦值操作符在減少左操作數的使用計數之前使rhs的使用計數加1,從而防止自身賦值”。哎,反正我是那樣理解的。當然,一來就可以按常規那樣:


[cpp]
if(this == &rhs) 
    return *this; 


運行結果:

U_ptr constructor called !
HasPtr constructor called ! use = 1
HasPtr copy constructor called ! use = 2
HasPtr copy constructor called ! use = 3
HasPtr copy constructor called ! use = 4
0 0
10000 10000
10 10
HasPtr distructor called ! use = 4
HasPtr distructor called ! use = 3
HasPtr distructor called ! use = 2
10
HasPtr distructor called ! use = 1
U_ptr distructor called !


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved