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; }
运行结果:
make it simple, make it happen