複製鏈接
請複製以下鏈接發送給好友

句柄類

鎖定
句柄類(智能指針smart point)是存儲指向動態分配(堆)對象指針的類。
中文名
句柄類
外文名
smart point
釋    義
存儲指向動態分配對象指針的類
特    點
自動刪除指向的對象外

句柄類句柄類定義

句柄類一般定義

句柄類,除了能夠在適當的時間自動刪除指向的對象外,他們的工作機制很像C++的內置指針智能指針在面對異常的時候格外有用,因為他們能夠確保正確的銷燬動態分配的對象。他們也可以用於跟蹤被多用户共享的動態分配對象。
在C++中一個通用的技術是定義包裝(cover)類或句柄(handle)類,也稱智能指針。句柄類存儲和管理基類指針指針所指向對象的類型可以變化,它既可以指向基類類型對象又可以指向派生類型對象。用户通過句柄類訪問繼承層次的操作。因為句柄類使用指針執行操作,虛成員的行為將在運行時根據句柄實際綁定的對象類型而變化,即實現c++運行時動態綁定。故句柄用户可以獲得動態行為但無需操心指針的管理。

句柄類理解實質

句柄類實際上通過複製指向引用計數器類型的指針,來代替複製實際對象;從而使得複製對象變成複製指針,實現虛擬複製(即是用複製對象的地址代替複製對象本身),以提高內存效率和訪問速度。在涉及到大型、複雜對象以及一些不能複製或是不易複製的對象的複製控制問題時,顯得特別有用。

句柄類句柄類涉及的技術

句柄類引入使用計數

定義句柄類或智能指針的通用技術是採用一個使用計數(use count)。句柄類將一個計數器與類指向的對象相關聯。使用計數跟蹤該類有多少個指針共享同一對象。當使用計數為0時,就刪除該類對象,否則再刪除類對象時,只要引用計數不為0,就不刪除實際的類對象,而是是引用計數減1,實現虛刪除。

句柄類使用計數類

為了便於理解,我們定義一個實際類(Point),一個引用計數器類(UPoint),一個句柄類(Handle),後面將有例子給以參考。
實現使用計數有兩種經典策略:一種是定義一個單獨的具體的類用以封裝使用計數和指向實際類的指針
另一種是定義一個單獨的具體的類用以封裝引用計數和類的對象成員。我們稱這種類為計數器類(UPoint)。在計數器類中,所有成員均設置為private,避免外部訪問,但是將句柄類Handle類聲明為自己的友元,從而使句柄類能操縱引用計數器。

句柄類寫時複製

寫時複製(copy on write)技術是解決如何保證要改動的那個引用計數器類UPoint對象不能同時被任何其他的句柄類(Handle類)所引用。通俗的來説,就是當實際對象Point被多個Handle類的指針共享時,如果需要通過指針改變實際對象Point,而其他的指針又需要保持原來的值時,這就有矛盾了。打個不恰當的比方來説,兩個以上的人共有5W塊錢,如果其中一個人想用這5W塊錢去消費,那就必須通知其他人。否則在這個人消費了5塊錢後,其他人還以為他們仍然有5W塊錢,如果這兒時候,他們去買5W的東西,就會發現錢變少了或是沒有了,此時他們就陷入債務的泥團。在C++中通過指針訪問已經刪除或是不存在的對象,將是非常危險的。有可能系統提示該行為未定義,也有可以內存非法訪問,還有可能使系統崩潰。

句柄類句柄類的實例

句柄類完成源碼程序

//句柄類的實例

//完成源碼程序

#include <iostream>
using namespace std;
//句柄類中封裝的實際對象類型;Point;
class Point
{
public:
    //默認構造函數和帶有兩個參數的構造函數;
    Point() : xval(0), yval(0){}
    Point(int x, int y) :xval(x), yval(y){}
    int x()const{ return xval; }
    int y()const{ return yval; }
    Point& x(int xv){ xval = xv; return*this; }
    Point& y(int yv){ yval = yv; return*this; }
    ~Point()
    {
        cout << "~Point!" << endl;//測試時用於分析對象的析構過程;
    }
protected:
private:
    int xval;
    int yval;
};
//引用類型計算器類;
class UPoint
{
    //所有的數據成員和函數成員均為:private類型;
    friend class Handle;//將句柄類聲明為友元類,便於訪問引用計數器;
    Point p;//聲明一個Point實際對象的數據類型;實現數據隱藏;
    int u;//引用計數器;
    //當創建一個handle句柄對象時,將通過引用計數類自動創建一個Point的真實對象;
    //同時使計數器U自動為1;
    UPoint() :u(1){}
    UPoint(int x, int y) :p(x, y), u(1){}
    UPoint(const Point& p0) :p(p0), u(1){}
    ~UPoint()
    {
        cout << "~UPoint!" << endl;//測試時用於分析對象的析構過程;
    }
};
//句柄類實際上通過複製指向引用計數器類型的指針,來代替複製實際對象;從而使得
//複製對象變成複製指針,提高內存效率和訪問速度;
class Handle
{
public:
    //句柄類的構造函數,主要通過複製句柄中指向基類的指針來實現對象複製的虛擬操作;
    //3個構造函數;
    Handle() :up(new UPoint){}
    Handle(int x, int y) :up(new UPoint(x, y)){}
    Handle(const Point &p) :up(new UPoint(p)){}
    //複製函數;使引用計數自動加1;
    Handle(const Handle&h) :up(h.up){ ++up->u; }
    //賦值函數;使賦值右側對象的引用計數自動加1;
    Handle&operator=(const Handle&h)
    {
        ++h.up->u;//賦值右側引用計數器加1;
        //如果up的引用計數器為1,就直接刪除up所指向的對象;
        //經驗,任何改變實際引用計數器狀態變化的操作都應該檢查引用計算器
        //是否為0,為0就應該刪除實際操作對象;
        if (--up->u == 0)
            delete up;
        up = h.up;
        return*this;//返回修改指針up地址值的自身兑現引用;
    }
    ~Handle()
    {
        //如果up所指的對象引用計數為u為最後一個,則應該刪除對象;
        if (--up->u == 0)
            delete up;
        cout << "~Handle!" << endl;
    }
    int x()const
    {
        return up->p.x();
    }
    Handle& x(int x0)//採用句柄的值語義;
    {
        //更改up對象的值時,當引用計數不為1時,則説明實際對象point關聯多個
        //handle的up指針,為了保持原來指針所指向對象的數據不被改變;此時
        //需要將該對象的引用計數減1,然後重新定義一個新引用計數器類型;
        if (up->u != 1)
        {
            //將原對象的引用計數減1;再申請一個引用計數器對象;
            //採用寫時複製技術解決更改Point對象時所帶來的問題;
            --up->u;
            up = new UPoint(up->p);
        }
        up->p.x(x0);
        return*this;
    }
    int y()const
    {
        return up->p.y();
    }
    Handle& y(int y0)
    {
        if (up->u != 1)
        {
            --up->u;
            up = new UPoint(up->p);
        }
        up->p.y(y0);
        return*this;
    }
    //protected:
private:
    UPoint*up;
};

int main(int argc, char*argv[])
{
    Handle h(23, 34), tmp(h);//定義句柄對象h,和tmp
    Handle val;//定義默認的句柄類對象;
    //通過句柄類執行實際類Point成員函數功能;
    cout << "handle::h" << h.x() << '\t' << h.y() << endl;
    cout << "handle::tmp" << tmp.x() << '\t' << tmp.y() << endl;
    cout << "handle::val" << val.x() << '\t' << val.y() << endl;
    //關鍵的一步val=h;將涉及寫時複製;因為h和tmp指針共享同一指針;
    val = h.x(100);//改變句柄類h所指對象point對象x的值後賦值給val句柄;
    cout << "handle::h" << h.x() << '\t' << h.y() << endl;
    cout << "handle::tmp" << tmp.x() << '\t' << tmp.y() << endl;
    cout << "handle::val" << val.x() << '\t' << val.y() << endl;
    return 0;
}

句柄類運行結果分析

handle::h 23 34
handle::tmp 23 34 //h和tmp句柄類對象實際共享同一Point(23,34)對象;
handle::val 0 0 //val默認指向實際對象Point(0,0);
~UPoint! //由於寫時複製時:up = new UPoint(up->p);創建了一個臨時UPoint
~Point! //對象;調用完後釋放,由於Point是UPoint的成員對象,所以先
//UPoint,然後是Point。
handle::h 100 34 //通過修改賦值val = h.x(100);後,h和val共享同一對象Point(100,34)
handle::tmp 23 34
handle::val 100 34
//依次釋放內存;
~Handle! //val句柄類對象;val(100,34)
~UPoint!
~Point!
~Handle! //tmp句柄類;tmp(23,34)
~UPoint!
~Point!
~Handle! //只釋放了一個句柄類Handle的指針,沒有實際對象;
請按任意鍵繼續. . .

句柄類與句柄類相關的類

代理(surrogate)類,又稱委託。(後續完善 [1] 
這篇關於句柄類的介紹,在理論部分還是挺不錯的,但舉的例子就不敢恭維了,UPoint類的目的是從Handle類中將“引用計數”功能分離出來單獨實現,讓Handle類專注於實現“內存管理”功能,因此UPoint類必須能夠實現自動計數,而不應該在Handle類中再來對其加減。
上面分析結果的最後一段:
~Handle! //val句柄類對象;val(100,34) ~UPoint! ~Point! ~Handle! //tmp句柄類;tmp(23,34) ~UPoint! ~Point! ~Handle! //只釋放了一個句柄類Handle的指針,沒有實際對象; 請按任意鍵繼續. . .
分析有誤,改為:
~Handle! //val句柄類對象;val(100,34)
~UPoint! //temp指向的UPoint對象
~Point! //temp指向的UPoint對象的內嵌Point對象
~Handle! //tmp句柄類;tmp(23,34)
~UPoint! //val和h共同指向的Upoint對象
~Point! //val和h共同指向的Upoint對象的內嵌對象Point
~Handle! //釋放h句柄類

句柄類句柄類例子

#include <iostream>
using namespace std;
//-----------------------------------------
class Point
{
private:
    int xval,yval;
public:
    Point():xval(0),yval(0) {}
    Point(int x,int y):xval(x),yval(y) {}
    int x()const
    {
        return xval;
    }
    int y()const
    {
        return yval;
    }
    Point& x(int xv)
    {
        xval=xv;
        return *this;
    }
    Point& y(int yv)
    {
        yval=yv;
        return *this;
    }
};
//------------------------------------------------------
class UseCount
{
private:
    int* p;
    UseCount& operator=(const UseCount&);
public:
    UseCount();
    UseCount(const UseCount&);
    ~UseCount();
    bool only();
    bool reattach(const UseCount&);
    bool make_only();
};
UseCount::UseCount():p(new int(1)) {}
UseCount::UseCount(const UseCount&u):p(u.p)
{
    ++*p;
}
UseCount::~UseCount()
{
    if (--*p==0)
    {
        delete p;
    }
}
bool UseCount::only()
{
    return *p==1;
}
bool UseCount::reattach(const UseCount& u)
{
    ++*u.p;
    if (--*p==0)
    {
        delete p;
        p=u.p;
        return true;
    }
    p=u.p;
    return false;
}
bool UseCount::make_only()
{
    if (*p==1)return false;
    --*p;
    p=new int(1);
    return true;
}
//-------------------------------------------
class Handle
{
private:
    Point* p;
    UseCount u;
public:
    Handle();
    Handle(int,int);
    Handle(const Point&);
    Handle(const Handle&);
    Handle& operator =(const Handle&);
    ~Handle();
    int x()const;
    Handle&x(int);
    int y()const;
    Handle&y(int);
};
Handle::Handle():p(new Point) {}
Handle::Handle(int x,int y):p(new Point(x,y)) {}
Handle::Handle(const Point&p0):p(new Point(p0)) {}
Handle::Handle(const Handle&h):u(h.u),p(h.p) {}
Handle::~Handle()
{
    if (u.only())
    {
        delete p;
    }
}
Handle& Handle::operator=(const Handle &h)
{
    if (u.reattach(h.u))delete p;
    p=h.p;
    return *this;
}
int Handle::x()const
{
    return p->x();
}
int Handle::y()const
{
    return p->y();
}
Handle& Handle::x(int x0)
{
    if (u.make_only())p=new Point(*p);
    p->x(x0);
    return *this;
}
Handle& Handle::y(int y0)
{
    if (u.make_only())p=new Point(*p);
    {
        p->y(y0);
        return *this;
    }
}
//---------------------------------------------------
int main()
{
    Handle h(3,4);
    Handle h2 = h;
    h2.x(5);
    int n = h.x();
    cout<<n<<endl;
    return 0;
}

參考資料
  • 1.    《Accelerated C++》中文版