C++ 沉思录——Chap6:句柄

第五章介绍了代理类,这个类能让我们在一个容器中存储类型不同但相互关联的对象。这种方法需要为每个对象创建一个代理,并要将代理存储在容器中。创建代理将会复制所代理的对象。
如果想避免这些复制该怎么做呢?可以使用句柄类。它允许在保持代理的多态行为的同时,还可以避免进行不必要的复制。处于多态的环境中,我们可以知道对象的基类类型,但是不知道对象本身的类型或者怎么样复制这种类型的对象。如果多个指针指向同一个对象,就必须考虑要在什么时候删除对象。不能太早也不能太晚,太早删除,就会有某个仍然指向它的指针存在,再使用这个指针就会产生未定义行为。删除得太晚又会占用本来早该另作它用的空间。需要一种方法,让我们避免某些缺点的同时又能够获取指针的某些优点,尤其是在能够保持多态性的前提下避免复制对象的代价。C++的解决方法就是定义一个适当的类。由于这些类的对象通常被绑定到它们所控制的对象上,所以这些类常被称为句柄类(handle class)。假定有这样一个类:

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;
     }
};

handle 应该“控制”它所绑定的对象,也就是说handle应该创建和销毁对象。有两种方式:可以创建自己的Point对象并把它赋给一个handle去进行复制,或者可以把用于创建Point的参数传给这个handle。我们要允许这两种方法,所以想让handle类的构造函数和Point类的构造函数一样。也就是,我们想用:

Handle h0(123,456);

来创建绑定到新分配的坐标为123和456的Point的handle,而用:

Handle h(p);

创建副本,并将handle绑定到该副本。这样,handle就可以控制对副本的操作。从效果上说,handle 就是一种包含单个对象的容器。当handle绑定到Point类之后,可以对 “->”进行重载,使用operator-> 将handle的所有操作转发给相应的Point操作来执行。但是这种操作也会过于暴露Point的操作,如果想绕开operator->(),就必须为handle提供自己的x和y操作,这两个操作要么返回int,要么返回Handle&。根据上面的分析给出Handle 类的大致轮廓:

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:
               // ..
     };

使用句柄原因之一就是为了避免不必要的对象复制,也就是说允许多个句柄绑定到单个对象上。我们必须了解有多少个句柄绑定在同一个对象上,只有这样才能确定应当何时删除对象。而通常使用引用计数来达到这个目的。但是,这个引用计数不能是句柄的一部分,否则句柄的设计会相当麻烦。也不能让引用计数成为对象的一部分,因为那样要求我们重写已经存在的对象类。我们必须定义一个新的类来容纳一个引用计数和一个Point对象。我们成为UPoint。这个类纯粹是为了实现而设计的,所以我们把其所有成员都设置为private,并且将我们的句柄类声明为友元。我们希望能以创建Point的全部方式创建UPoint对象,所以:

class UPoint
     {
          friend class Handle;
          Point P;
          int     u;

          UPoint() : u(1) { } // 引用计数初始化为1
          UPoint(int x , int y) : P(x, y), u(1) { }
          UPoint(const Point & p0) : P(p0), u(1) { }
     }

现在可以完善Handle类了

class Handle
{
private:
     UPoint *up;    //和间接层UPoint打交道了
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
对象指向同一个对象来避免复制。定义句柄类,我们还需要新定义一个类来容
纳被引用类对象的引用计数和被引用类对象本身。

这里的引用计数为0时删除p的意思应该是由handle创建的p
handle的构造函数中有一种是跟p有关的,在这类构造函数中
创建了p并初始化了p的计数,当这个计数为0时删除的是由handle创建
的p,如果Point自身不实例化对象的话,这样就真的实现了删除p对象了

可见,handle需要提供hanle(const handle &) , hanle(const Point &)和hanle(int x, int y)
三类构造函数的必要性,都是为了创建Point对象,而且这样创建的Point对象可以与handle关联

*/
#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; y
        return *this;
    };
    Point& y(int yv) 
    { 
        yval = yv; 
        return *this;
    };
private:
    int xval, yval;
};

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(): up(new UPoint){};
    Handle(int x,int y): up(new UPoint(x,y)){};//按创建Point的方式构造handle,handle->UPoint->Point
    Handle(const Point& p): up(new UPoint(p)){};//创建Point的副本
    Handle(const Handle& h): up(h.up){ ++up->u;};//此处复制的是handle,但是底层的point对象并未复制,只是引用计数加1
    Handle& operator=(const Handle& h)
    {
        ++h.up->u;                //右边的对象引用计数加1,左边的减1
        if(--up->u == 0)
            delete up;
        up = h.up;
        return *this;
    };
    ~Handle()
    {
        if(--up->u == 0)
        delete up;
    };
    int x() const{return up->p.x();};
    Handle& x(int xv)
    {
        up->p.x(xv); 
        return *this;
    };

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

    Handle& y(int yv)
    {
        up->p.y(yv); 
        return *this;
    };
    int OutputU(){return up->u;};   //输出引用个数
private:
    UPoint* up;
};

int main()
{
    //Point *p = new Point(8,9); 
    Point p(8,9);
    //Point p1 = p.x(88);
    //Point *pp = &p;
    Handle h1(1,2);
    Handle h2 = h1;        //此处调用的是构造函数Handle(const Handle& h)
    h2.x(3).y(4);               //此处的特殊写法是因为写xy函数内返回了对象
    Handle h3(5,6);        //此处调用Handle的赋值运算符重载函数Handle& operator=(const Handle& h)
    h1 = h3;
    Handle h4(p);
    Handle h5(h4);
    h4.x(7).y(8);
    //Handle h5(p1);
    //Handle h5 = h4;
    cout <<"h1(" << h1.x() <<":"<< h1.y() << "):" << h1.OutputU() <<endl;
    cout <<"h2(" << h2.x() <<":"<< h2.y() << "):" << h2.OutputU() <<endl;
    cout <<"h3(" << h3.x() <<":"<< h3.y() << "):" << h3.OutputU() <<endl;
    cout <<"h4(" << h4.x() <<":"<< h4.y() << "):" << h4.OutputU() <<endl;
    cout <<"h5(" << h5.x() <<":"<< h5.y() << "):" << h5.OutputU() <<endl;
    //delete pp; //不能这样,不是用new分配的空间
    //cout <<"h5(" << h5.x() <<":"<< h5.y() << "):" << h5.OutputU() <<endl;
    cout<<p.x()<<" "<<p.y()<<endl;
    //cout<<&p1<<endl;
    return 0;
}

运行结果:
C++ 沉思录——Chap6:句柄
原文链接: https://www.cnblogs.com/zhuyp1015/archive/2012/07/26/2610847.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/56823

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 上午7:39
下一篇 2023年2月9日 上午7:39

相关推荐