1. 程式人生 > >[8]C++智慧指標的簡單實現

[8]C++智慧指標的簡單實現

智慧指標(smart pointer)是儲存指向動態分配(堆)物件指標的類,用於生存期控制,能夠確保自動正確的銷燬動態分配的物件,防止記憶體洩露。它的一種通用實現技術是使用引用計數(reference count)。智慧指標類將一個計數器與類指向的物件相關聯,引用計數跟蹤該類有多少個物件共享同一指標。每次建立類的新物件時,初始化指標並將引用計數置為1;當物件作為另一物件的副本而建立時,拷貝建構函式拷貝指標並增加與之相應的引用計數;對一個物件進行賦值時,賦值操作符減少左運算元所指物件的引用計數(如果引用計數為減至0,則刪除物件),並增加右運算元所指物件的引用計數;呼叫解構函式時,建構函式減少引用計數(如果引用計數減至0,則刪除基礎物件)。

智慧指標就是模擬指標動作的類。所有的智慧指標都會過載 -> 和 * 操作符。智慧指標還有許多其他功能,比較有用的是自動銷燬。這主要是利用棧物件的有限作用域以及臨時物件(有限作用域實現)解構函式釋放記憶體。當然,智慧指標還不止這些,還包括複製時可以修改源物件等。智慧指標根據需求不同,設計也不同(寫時複製,賦值即釋放物件擁有許可權、引用計數等,控制權轉移等)。auto_ptr 即是一種常見的智慧指標。

包含指標的類需要特別注意複製控制,原因是複製指標時只複製指標中的地址,而不會複製指標指向的物件。

大多數C++類用三種方法之一管理指標成員
    (1)不管指標成員。複製時只複製指標,不復制指標指向的物件。當其中一個指標把其指向的物件的空間釋放後,其它指標都成了懸浮指標。這是一種極端
    (2)當複製的時候,即複製指標,也複製指標指向的物件。這樣可能造成空間的浪費。因為指標指向的物件的複製不一定是必要的。
    (3) 第三種就是一種折中的方式。利用一個輔助類來管理指標的複製。原來的類中有一個指標指向輔助類,輔助類的資料成員是一個計數器和一個指標(指向原來的)(此為本次智慧指標實現方式)。

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

智慧指標通常用類模板實現:

template <class T>
class smartpointer
{
private:
	T *_ptr;
public:
	smartpointer(T *p) : _ptr(p)  //建構函式
	{
	}
	T& operator *()        //過載*操作符
	{
		return *_ptr;
	}
	T* operator ->()       //過載->操作符
	{
		return _ptr;
	}
	~smartpointer()        //解構函式
	{
		delete _ptr;
	}
};

程式碼一:

class Point                                       //基礎物件類,要做一個對Point類的智慧指標
{
public:
    Point(int xVal = 0, int yVal = 0):x(xVal),y(yVal) { }
    int getX() const {        return x;    }
    int getY() const {        return y;    }
    void setX(int xVal) {        x = xVal;    }
    void setY(int yVal) {        y = yVal;    }
private:
    int x,y;
};
class RefPtr                                  //輔助類
{//該類成員訪問許可權全部為private,因為不想讓使用者直接使用該類
    friend class SmartPtr;      //定義智慧指標類為友元,因為智慧指標類需要直接操縱輔助類
    RefPtr(Point *ptr):p(ptr), count(1) { }
    ~RefPtr() {        delete p;    }

    int count;      //引用計數
    Point *p;       //基礎物件指標
};

class SmartPtr      //智慧指標類
{
public:
    SmartPtr(Point *ptr):rp(new RefPtr(ptr)) { }    //建構函式
    SmartPtr(const SmartPtr &sp):rp(sp.rp) {	    //複製建構函式
		++rp->count; 
    }
    SmartPtr& operator=(const SmartPtr& rhs) {       //過載賦值操作符
        ++rhs.rp->count;                             //首先將右運算元引用計數加1,
        if (--rp->count == 0)                        //然後將引用計數減1,可以應對自賦值
            delete rp;
        rp = rhs.rp;
        return *this;
    }
    ~SmartPtr() {                                    //解構函式
        if (--rp->count == 0)                        //當引用計數減為0時,刪除輔助類物件指標,從而刪除基礎物件
            delete rp;
    }

private:
    RefPtr *rp;                                      //輔助類物件指標
};

int main()
{
    Point *p1 = new Point(10, 8);
    SmartPtr sp1(p1);     //此時sp1.rp->count = 1
    SmartPtr sp2(sp1);    //首先將sp1.rp->count賦給sp2.rp->count,之後sp2.rp->count++,這時sp1,sp2的rp是同一個物件
    Point *p2 = new Point(5, 5);
    SmartPtr sp3(p2);
    sp3 = sp1;

    return 0;
}

程式碼二:

#include<iostream>
using namespace std;

// 定義僅由HasPtr類使用的U_Ptr類,用於封裝使用計數和相關指標
// 這個類的所有成員都是private,我們不希望普通使用者使用U_Ptr類,所以它沒有任何public成員
// 將HasPtr類設定為友元,使其成員可以訪問U_Ptr的成員
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:
    // 建構函式:p是指向已經動態建立的int物件指標
    HasPtr(int *p, int i) : ptr(new U_Ptr(p)) , val(i)
    {
        cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
    }

    // 複製建構函式:複製成員並將使用計數加1
    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&);

    // 解構函式:如果計數為0,則刪除U_Ptr物件
    ~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物件
    int get_ptr_val() const
    {
        return *ptr->ip;
    }
    void set_ptr_val(int i)
    {
        *ptr->ip = i;
    }
private:
    U_Ptr *ptr;   //指向使用計數類U_Ptr
    int val;
};
HasPtr& HasPtr::operator = (const HasPtr &rhs)  //注意,這裡賦值操作符在減少做運算元的使用計數之前使rhs的使用技術加1,從而防止自我賦值
{
    // 增加右運算元中的使用計數
    ++rhs.ptr->use;
    // 將左運算元物件的使用計數減1,若該物件的使用計數減至0,則刪除該物件
    if (--ptr->use == 0)
        delete ptr;
    ptr = rhs.ptr;   // 複製U_Ptr指標
    val = rhs.val;   // 複製int成員
    return *this;
}

int main(void)
{
    int *pi = new int(42);
    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;
}
執行結果如下圖:

轉載:
http://blog.csdn.net/hackbuteer1/article/details/7561235
http://www.cnblogs.com/yangshaoning/archive/2012/03/18/cpp_smart_pointer.html