句柄类

Posted on 2020-08-28 10:46  金色的省略号  阅读(722)  评论(0编辑  收藏  举报

  句柄类, 来自于C++ 沉思录的概念

  百度百科: 

  句柄(handle)是C++程序设计中经常提及的一个术语,它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念,句柄一般是指获取另一个对象的方法    ——     一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系

  句柄(handle)类,它允许在 保持代理的多态行为 的同时,还可以避免进行不必要的复制。 

  一、问题的提出

  通常参数和返回值是通过复制传递的;

  只要几个指针指向同一个对象,就必须考虑在什么时候删除这个对象;

  在保持多态性的前提下避免复制对象;

  handle类的对象 通常被绑定到 它们所控制的类的对象,这些 handle的行为类似指针所以人们有时也叫它们智能指针

  将handle直接绑定到对象p上,则我们的handle最好与p的内存分配和释放无关;

#include <iostream>
using namespace std;

class Point{
public:
    Point():xval(0),yval(0) { }               //缺省/无参构造
    Point(int x, int y): xval(x),yval(y) { }  //有参构造
    int x() const { return xval; }            //返回xval
    int y() const { return yval; }            //返回yval
    Point& x(int xv) { xval = xv; return *this; } //更改xval值返回对象
    Point& y(int yv) { yval = yv; return *this; } //更改yval值返回对象
private:
    int xval, yval;
};

/* handle可以控制对Point副本的操作,
    是一种只包含单个Point对象的容器 
    */
class Handle{    
public:
    Handle(int x, int y){
        p = new Point(x,y);
    }
    Handle(Point& p){
        this->p = new Point(p.x(),p.y()); //创建一个p的副本
    }
    Point* operator->(){
        return p;
    }; //Point* add = h.operator->();
    ~Handle(){ delete p; }
private:
    Point* p;
};

int main()
{
    Point p(1,2);
    
    /* 将handle直接绑定到对象p上,则我们的handle与p的内存分配和释放无关 */
    Handle h0(123,456); //h0绑定到坐标123,456的Point
    Handle h(p); //h创建一个p的副本,并将handle绑定到该副本,控制对副本的操作
    
    cout << h->x() << endl; //使用 operator-> 将Handle的所有操作转发给相应的Point操作来执行
    cout << h.operator->()->y() << endl; //h.operator->() 返回值Point*
    
    cout << h0->x() << endl;
    cout << h0.operator->()->y() << endl;
    return 0;
} 
View Code

  二、简单的实现,必须明确地选择让我们的handle类支持哪些Point操作,避免使用operator->隐藏Point对象的真实地址

  通过无参构造,x,y参数构造,Point对象引用,Handle对象引用复制 (拷贝)构造,通过Handle对象对另一个Handle对象的赋值,使得多个Handle对象指向同一个Point对象而不复制Point对象Point对象在没有Handle对象指向的时候释放 

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; }
private:
    int xval, yval;
};

//为了避免不必要的对象复制,得允许多个句柄绑定到单个对象上
//我们必须定义一个新的类来容纳一个引用计数和一个Point对象,称之为UPoint

class UPoint{//所有成员都是私有的   
    friend class Handle;
    Point p;
    int u; 
    UPoint():u(1) { }
    UPoint(int x, int y): p(x,y), u(1) { }
    UPoint(const Point& p0):p(p0), u(1) { }
};

class Handle{
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);
private:   
    UPoint * up;//添加的
};

Handle::Handle():up(new UPoint) { }
Handle::Handle(int x,int y):up(new UPoint(x,y)) { }
Handle::Handle(const Point& p):up(new UPoint(p)) { }
Handle::~Handle()
{
    if(--up->u == 0)
        delete up;
}
Handle::Handle(const Handle& h):up(h.up) { ++up->u; }
Handle& Handle::operator=(const Handle& h)
{
    ++h.up->u;
    if(--up->u == 0)
        delete up;
    up = h.up;
    return *this;
}

int Handle::x() const { return up->p.x(); }
int Handle::y() const { return up->p.y(); }

//指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值
Handle& Handle::x(int x0)
{
    up->p.x(x0);
    return *this;
}
Handle& Handle::y(int y0)
{
    up->p.y(y0);
    return *this;
}
View Code

  代码测试

#include <iostream>
using namespace std;

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; }
private:
    int xval, yval;
};

//为了避免不必要的对象复制,得允许多个句柄绑定到单个对象上
//我们必须定义一个新的类来容纳一个引用计数和一个Point对象,称之为UPoint

class UPoint{ /* //所有成员都是私有的 */
    friend class Handle;
    Point p;
    int u; 
    UPoint():u(1) { }
    UPoint(int x, int y): p(x,y), u(1) { }
    UPoint(const Point& p0):p(p0), u(1) { }
}; 
 
class Handle{
/*private:
    class UPoint{ //可以设置为内部类,所有成员Handle均可见,外不可见
    public:    
        int u; 
        Point p;
        UPoint():u(1) { }
        UPoint(int x, int y): p(x,y), u(1) { }
        UPoint(const Point& p0):p(p0), u(1) { }
    }; */
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);
    int UPoint_u(); //测试用
private:   
    UPoint * up; 
};

Handle::Handle():up(new UPoint) { }
Handle::Handle(int x,int y):up(new UPoint(x,y)) { }
Handle::Handle(const Point& p):up(new UPoint(p)) { }
Handle::~Handle()
{
    if(--up->u == 0)  //原来的Point对象一旦没有Handle对象指, 即释放
        delete up;
    //cout << "~Handle()\n"; //测试用
}
Handle::Handle(const Handle& h):up(h.up) { ++up->u; }
Handle& Handle::operator=(const Handle& h)
{
    ++h.up->u;    // h指向的Point对象的指针数增一
    if(--up->u == 0) //Handle对象原来指向的Point对象的指针数减一
        delete up;   //原来的Point对象一旦没有Handle对象指, 即释放
    up = h.up;    //Handle对象的指针, 指向h指向的Point对象
    return *this;
}

int Handle::x() const { return up->p.x(); }
int Handle::y() const { return up->p.y(); }

//指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值
Handle& Handle::x(int x0)
{
    up->p.x(x0);
    return *this;
}
Handle& Handle::y(int y0)
{
    up->p.y(y0);
    return *this;
}

//测试用
int Handle::UPoint_u(){
    return up->u;
}

//多个Handle对象, 指向同一个Point对象p, 一样的坐标不一定是同一个对象
int main()
{
    Handle h1(123,456);
    Handle h2 = h1;
    Handle h3;
    h3 = h2;
    h3.y(789);    
    cout << h3.UPoint_u() << endl;
    cout << h2.y() << endl;
    Handle h4(123,789);
    cout << h4.UPoint_u() << endl;
    return 0;
}
View Code

  三、实现的改进引用计数与Point对象本身分离,在句柄中额外加上一些信息 int * u

  Handle类数据成员, 使用Point* ,不仅能够将一个Handle绑定到一个Point,还能将其绑定到一个继承自Point类的对象

  Handle类参数为 Point引用  的复制构造函数,复制一个副本,即一个Point 对象副本,即参数的副本,该副本为动态构造

#include <iostream>
using namespace std;

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; }
private:
    int xval, yval;
};

class Handle{
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);
    int handle_u(){ return *u; } //测试用
private:   
    Point* p; 
    int* u;
};

Handle::Handle():u(new int(1)), p(new Point) { }
Handle::Handle(int x,int y):u(new int(1)), p(new Point(x,y)) { }
Handle::Handle(const Point& p0):u(new int(1)), p(new Point(p0)) { }
Handle::~Handle()
{
    if(--*u == 0){ 
        delete u;
        delete p;
    }
}
Handle::Handle(const Handle& h):u(h.u), p(h.p) { ++*u; }
Handle& Handle::operator=(const Handle& h)
{
    ++*h.u;    // h指向的Point对象的指针数增一
    if(--*u == 0){ //Handle对象原来指向的Point对象的指针数减一
        delete u;   
        delete p; //原来的Point对象一旦没有Handle对象指, 即释放
    }
    u = h.u;
    p = h.p;
    return *this;
}

int Handle::x() const { return p->x(); }
int Handle::y() const { return p->y(); }

//指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值
Handle& Handle::x(int x0)
{
    p->x(x0);
    return *this;
}
Handle& Handle::y(int y0)
{
    p->y(y0);
    return *this;
}

//多个Handle对象, 指向同一个Point对象
int main()
{
    Handle h1(123,456);
    Handle h2 = h1;
    Handle h3;
    h3 = h2;
    h3.y(789);    
    cout << h3.handle_u() << endl;
    cout << h2.y() << endl;
    Handle h4(123,789);
    cout << h4.handle_u() << endl;
    return 0;
}
View Code

  四、总结

  句柄通过绑定类的指针与绑定的类对象或其子类对象进行绑定,之后,通过句柄的计数器使多个句柄与之进行绑定只要有一个句柄指向被绑定对象,该对象都不会释放,不因其他绑定句柄改变指向或销毁而释放,保证了被绑定对象的安全,也保证了与之绑定的句柄都有具体的指向; 绑定对象为动态构造 ,句柄的成员函数包装绑定类对象的类成员函数,使得句柄对象控制绑定对象的行为,与绑定对象行为相似

  句柄的实质,就是为了指针的安全

  句柄的 参数为类对象(非句柄类对象)的 复制构造函数,会复制类对象,然后绑定复制的类对象

  绑定同一个类对象的句柄,其计数器空间、绑定的类对象的空间是一样的

  句柄对象析构时,计数器值会减一,句柄对象的成员变量会释放只要计数器值不为0句柄对象成员变量指向的空间不会释放(包括计数器空间,绑定类对象空间)

    //测试代码
    Handle h1(123,456);
    Handle h2(123,456); //与h1绑定的不是同一个对象
    {
        Point p(8,9); // p出了作用域销毁 
        Handle h3(p); //用p的引用创建绑定对象, h3出了作用域销毁
        h2 = h3;      //改变h2指向
        cout << h2.handle_u() << endl; //h2,h3绑定同一个对象
    }
    cout << h2.handle_u() << endl; //大括号作用域里绑定的对象依然存在
    cout << h2.x() << "," << h2.y() << endl;
View Code 

  五、句柄计数器的抽象