一:
在代理类的帮助下,我们已经可以实现在一个容器里存储一个类层次里所有类型的对象,但是代理有一个很明显的缺点,就是需要复制对象,当一个对象非常大或者是一种不能轻易复制的资源的时候,这个实现遇到了很大的困难,于是我们有了句柄(handle)类这个技术。
我们有这么一个类
class point { public: point():x_cdt(0),y_cdt(0){} point(int x,int y):x_cdt(x),y_cdt(y){} int get_x() {
return x_cdt;
} int get_y() {
return y_cdt;
} point& set_x(int x)//返回值是point&是为了实现这样的表达 //p.set_x(2).set_y(4); { x_cdt = x; return *this; } point& set_y(int y) { y_cdt = y; return *this; }
private: int x_cdt,y_cdt; };
|
这个类有一切他应该有的成员函数,然后我们希望有这么一个handle能够替代实现他的所有功能,而又保证不需要复制这个对象,我们能想到的第一个方法当然是得到这个对象的指针但是这样我们就又把内存的分配暴露到了用户面前,可以随意的得到底层的指针。这个handle第一次是写成这样的:
class handle { public: handle(); handle(int x,int y); handle(const point&); handle(const handle&); handle& operator=(const handle&); ~handle(); private: point* operator->(); }
|
我们暂时没有把set_x()这样的成员先假如到这里,为了简便。当我们写完之后,我们又遇到了时刻困扰我们的问题,就是对象的删除,既然我们允许把一个对象绑定到多个handle上,当其中的一个handle删除的时候,这个对象会怎么样呢?这个问题是要解决的。这里,我们呢引入了引用计数的概念,很显然,我们不能把这个引用计数放到我们的handle类中,因为一旦我们这么做了,当我们用一个handle绑定到一个对象的时候,他必须知道其他所有绑定到这个对象的handle的引用计数,然后更新这个计数,这非常困难。那么如果我们把这个引用计数放到对象本身呢?稍微思考一下就知道也是不合理的,这样做意味着我们每次都要改写对象。所以,综上考虑,我们决定写一个新的类来存放这个引用计数,这个类是完全为了技术的实现设计的,所以我们把他的所有成员设置成private,而让handle成为他的友元类,具体是这样的:
class count_point { friend class handle; point p; int count; count_point(); count_point(int x,int y); count_point(const count_point&); };
|
有了这个引用计数,我们就可以让我们之前的一个小忧虑变得合理点,就是我们在handle中有一个 point* operator->(); 现在我们这样改写这个handle类:
class handle { public: handle(); handle(int x,int y); handle(const point&); handle(const handle&); handle& operator=(const handle&); ~handle(); private: count_point* ptr; };
|
好了,至此,框架已经行程,我们来实现句柄。
class count_point { friend class handle; point p; int count; count_point():count(1){} count_point(int x,int y):p(x,y),count(1){} count_point(const point&h):p(h),count(1){} };
|
然后是handle类:
class handle { public: handle():ptr(new count_point){} handle(int x,int y):ptr(new count_point(x,y)){} handle(const point&h):ptr(new count_point(h)){} handle(const handle&h):ptr(h.ptr){++ptr->count;} handle& operator=(const handle&h) { ++h.ptr->count; if(--ptr->count == 0) delete ptr; ptr = h.ptr; return *this; } ~handle() { if(--ptr->count == 0) delete ptr; } private: count_point* ptr; };
|
最后,我们还有一个小任务,就是实现handle中的set_x()和set_y()成员,注意当初我们为了看上去简便暂时没有把他们添加到handle中,事实上他们是应该被包含的,我们来定义一个
handle::set_x(int x) { ptr->p.set_x(x); return *this; }
|
不得不提醒,这里涉及到指针语义和值语义的问题,我们这样做,是选择了指针语义,这样做的后果就是,你改动一个handle绑定的对象将使所有与这个对象绑定的handle发生一样的变化,这样可以避免复制。 | | |
二:
在我的前一篇博文里,我们讨论了有关句柄类的实现,这个句柄类有一个明显的缺陷,那就是必须知道你要代理的那个类的具体类型,如果是一个继承类的话,我们之前的实现就出问题了。这个问题就是
class count_point { friend class handle; point p;//这里
int count; count_point(); count_point(int x,int y); count_point(const count_point&);
};
|
现在如果我们把引用计数从count_point类中分离出来,也就是我们这样写handle类
class handle { public: handle(); handle(int x,int y); handle(const point&); handle(const handle&); handle& operator=(const handle&); ~handle(); private: point* ptr; int* u;//指向引用计数
}
|
使用了这个point的指针后我们就可以把这个handle绑定到不管是一个基类还是他的派生类,这是动态绑定的优点。这里我们发现舍去了之前我们写的count_point类,看上去简洁了很多,以后我们会回头来写一个辅助类。好了,现在要做的就是实现handle类
handle::handle():ptr(new point),u(new int(1)){}
handle::handle(int x,int y):ptr(new point(x,y)),u(new int(1)){}
handle::handle(const point&h):ptr(new point(h)),u(){}
handle::handle(const handle&h):ptr(h.p),u(h.u) { ++*u; }
handle::handle& operator=(const handle&h) { ++*h.u; if(--*u == 0){ delete u; delete ptr; } u = h.u; ptr = h.ptr; return *this; }
|
现在我们希望最好我们能把引用计数也抽象一下,应该怎么抽象呢?最好的就是用类类实现,把int*这个计数放到一个类里,这个类具有必要的初始化函数,拷贝构造函数,赋值操作符,析构函数,于是我们构造了这样的一个类
class usecount { public: usecount(); usecount(const usecount&); usecount& operator=(const usecount&); ~usecount(); private: int* p; }
|
按照惯例,现在我们要做的就是实现它
usecount::usecount():p(new int(1)){}
usecount::usecount(const usecount& h):p(h.p) {++*p;}
usecount::~usecount() { if(--*p == 0) delete p; }
|
注意我们没有实现赋值操作符,等下会解释为什么这么做,现在我们可以改写handle类了
class handle { public: handle(); handle(int x,int y); handle(const point&); handle(const handle&); handle& operator=(const handle&); ~handle(); private: point* ptr; usecount u;//改成这个 }
|
按照老习惯,接下来要实现这个类的成员函数
handle::handle():ptr(new point){}
handle::handle(int x,int y):ptr(new point(x,y)){}
handle::handle(const point&h):ptr(new point(h)){}
handle::handle(const handle&):ptr(h.ptr),u(h.u){}
handle::~handle() { if(--u == 0) delete ptr; }
|
恩,是时候交代一下我们为什么没有写operator=函数了,这个函数要做什么?他要对一个计数器加一,对另外一个减一,可能还要删除一个计数器,这些操作很可能改变引用计数,所以我们要在usecount类中添加一个额外的操作,我们命名为reattach,因为我们不定义operator=了,所以他应该在类中成为一个私有成员,现在看看reattach的实现
bool usecount::reattach(const usecount&h) { ++*h.p; if(--*p == 0){ delete p; p = h.p; return true; } p = h.p; return false;
}
|
现在我们可以定义handle的operator=了
handle::operator =(const handle& h) { if(u.reattach(h.u)) delete ptr; ptr = h.ptr; return *this; }
|