博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
句柄类
阅读量:7034 次
发布时间:2019-06-28

本文共 10808 字,大约阅读时间需要 36 分钟。

代理类,这个类能让我们在一个容器中存储类型不同但相互关联的对象。创建代理将会复制所代理的对象,如果想避免这些复制该怎么办?

通常参数和返回值是通过复制自动传递的,用引用传递参数可以避免这样的复制,但是这样对返回值来说不太容易(比如局部对象)。同样可以用指针来避免复制,但是使用指针有几个问题:

(1)使用对象指针比直接使用对象要困难。

(2)未初始化的指针非常危险,在c++实现中,只要对这样的指针实施复制操作就会出现问题。

(3)无论何时,只要有几个指针指向同一个对象,就必须考虑在什么时候删除这个对象。

为了能够在保持多态性的前提下避免复制对象,c++解决方法就是定义句柄类,将句柄类的对象绑定到它们所控制的类的对象上。

一 怎样绑定(将对象绑定到句柄)? 这里有两种选择:

(1)可以创建自己的对象并把它赋给一个handle去进行复制。

有对象p, 则 Handle h(p) 将创建p的一个副本,并将handle绑定到该副本。handle 可以控制对该对象副本的操作。

(2)可以把用于创建对象的参数传递给这个handle

    有类Point ,创建一个对象方式为:Point p(123,456). 则Handle h(123, 456) 将创建一个句柄,该句柄绑定到point(123,456)。

从效果上说,handle就是一种只包含单个对象的容器。

二 已经将句柄绑定到对象,怎样获取对象?

要是一个handle在行为上类似一个指针,则可以使用operator->将handle的所有操作都转发给相应的Point操作来执行。Point* operator->();    Point * addr = h.operator->() 这样就可以获取对象地址了。如果我们希望handle能够对对象的分配和回收拥有完全的控制权,则最好能够阻止用户直接获得对象的实际地址。所以,如果想把真实的地址隐藏起来,就必须避免使用operator->,而且必须明确的选择让我们的handle类支持哪些Point操作(可以在handle类中照搬Point类中所支持的操作)。

三 句柄类已经设计好了,它到底怎样避免对象复制的?

考虑函数传参的问题,把一个句柄作为参数传给函数,我们希望的是复制句柄而不复制对象。(句柄类和代理类的区别是:句柄类复制的时候只是复制派生类的句柄而不是复制派生类的对象,而代理类复制的时候是复制派生类的对象)为此,我们必须了解有多少个句柄绑定到同一个对象上,只有这样才能确定在何时删除对象,通常使用引用计数来达到这个目的。重新设计一个引用计数类,这个类包含一个引用计数和一个Point对象,称之为UPoint。个人感觉这个UPoint就是更高一层次的抽象,以后handle只要和UPoint打交道就行了,然后由UPoint和Point打交道。UPoint的全部构造方式和Point的一样。这个类纯粹是为了实现而设计的,实际运用中根本没有这个类。因此其所有成员函数都设为私有。以下是几个类的实现代码,有详细注释:

 

#include <iostream>

using namespace std;

class Point

{
private:
int xval, yval;
public:
Point() : xval(0), yval(0)
{
   cout << "Point" << endl;
}
Point(int x, int y) : xval(x), yval(y)
{
   cout << "Point" << endl;
}
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;
}
};

class UPoint    //这个类对用户来说是不可见的, 就是一个间接层

{
friend class Handle;
Point p;    //Point 对象
int u;       //计数器
UPoint():u(1)   //提供所有的point的构造方式,这样就可以用任何形式的point对象来实例化UPoint类
{
   cout << "UPoint::" << u << endl;
}
UPoint(int x, int y):p(x, y), u(1)
{
   cout << "UPoint::" << u << endl;
}
UPoint(const Point &p0) : p(p0), u(1)
{
   cout << "UPoint::" << u << endl;
}
~UPoint()
{
   cout << "~UPoint::" << u << endl;
}
};

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():up(new UPoint)

{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(int x, int y):up(new UPoint(x, y)) //按创建Point的方式构造handlehandle->UPoint->Point,handle类只和Upoint类打交道,Upoint类只和Point类打交道
{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(const Point &p):up(new UPoint(p)) //创建Point的副本
{
cout << "Handle::" <<up->u << endl;
}
Handle::~Handle()
{

if(--up->u == 0)//当引用计数的值为0,则删除Upoint的对象(当最后一个句柄对象被析构时,才释放对象

{
   cout << "~Handle::" <<up->u << endl;
   delete up;
}
else
{
   cout << "~Handle::" <<up->u << endl;
}
}
Handle::Handle(const Handle &h):up(h.up)
{
++up->u;       //此处复制的是handle,但是底层的point对象并未复制,只是引用计数加1 (复制的只是句柄,并未复制对象。而代理类复制的却是对象)
(复制的只是指向对象的指针,而并未复制对象。正因为不用复制对象,所以相比代理类复制对象的做法开销小。)
cout << "Handle::" <<up->u << endl;
}
Handle& Handle::operator=(const Handle &h)
{
++h.up->u;       //右边的对象引用计数加1,左边的减1,这样可以确保当左右两个句柄引用同一个Upoint对象时也能正常工作。(可参考沉思录 P61)
if(--up->u == 0)
   delete up;
up = h.up;
cout << "Handle::" <<up->u << endl;
return *this;
}

int Handle::x() const

{
return up->p.x();
}

int Handle::y() const

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

void f(Handle h)   //测试函数,复制handle,但未复制Point

{
cout << h.x() << h.y() << endl;
}
int main()
{
Handle h(3,5);
cout << h.x() << h.y() << endl;
f(h);
return 0;
}

 

 

另外:

 

代理类有很多好处,但是麻烦的是每次都得进行复制.如果该类是经常使用并且member很多的话,这样复制的消耗是十分客观的.

 

因此这里就要介绍另外一种代理类,Handle,也就是句柄类.

 

 

 

为何使用句柄类?

 

首先就是复制问题.前面有谈到,有些类内部数据很多,采用复制消耗非常大,这种情况下就必须采用句柄类来进行操作.

 

其次是由于函数的参数和返回值都是采用了复制进行自动传递.虽然c++中引用可以避免,但是很多情况下返回值采用引用并不明智.

 

对于采用指针的方式,可以解决问题,但是又会引入调用者对于动态管理内存的麻烦.而这往往是很多错误的根源.

 

 

 

何为句柄类呢?

 

句柄类可以理解为采用了引用计数的代理类.

 

其多个句柄共享了同一个被代理的类.通过引用计数的方式来减少复制以及内存管理.

 

其行为类似指针,因此也有智能指针之称,但其实差别很大.后面会有讲述.

 

 

 

句柄类例子:

 

先有一个简单的类Point

 

1 class Point  2 {
/*{
{
{
*/ 3 public: 4 Point():_x(0),_y(0){} 5 Point(int x,int y):_x(x),_y(y){} 6 int x()const {
return _x;} 7 void x(int xv) { _x = xv;} 8 int y()const { return _y;} 9 void y(int yv) { _y = yv;} 10 private: 11 int _x; 12 int _y; 13 };/*}}}*/

 

接下来我们要定义其的Handle类.

 

我们的Handle类:

 

1 class Handle  2 {
3 public: 4 Handle():up(new UPoint){} 5 Handle(int x,int y):up(new UPoint(x,y)){} 6 Handle(const Point&p):up(new UPoint(p)){} 7 Handle(const Handle &h); 8 ~Handle(); 9 Handle& operator=(const Handle &h); 10 int x() const{ return up->p.x(); } 11 int y() const{ return up->p.y(); } 12 Handle& x(int); 13 Handle& y(int); 14 15 16 private: 17 UPoint *up; 18 void allocup(); 19 };

 

这里说明我们的Handle和指针的不同之处.

 

也许有读者会对Handle有疑问,为什么不采用operator->来直接操作point呢?

 

其实顾虑就是operator->返回的是point的地址.也就是使用者可以轻易的获得point的地址进行操作,这并不是我们想要的.这也就是Handle也pointer不想同的地方.

 

UPoint是为了采用引用计数定义的数据结构

 

1 //all member is private..only assess by Handle  2 class UPoint  3 {
/*{
{
{
*/ 4 friend class Handle; 5 6 Point p; 7 int u;//count 8 9 UPoint():u(0){} 10 UPoint(const Point&pv):p(pv){} 11 UPoint(int x,int y):p(x,y),u(1){} 12 UPoint(const UPoint &up):p(up.p),u(1){} 13 };/*}}}*/

 

 

 

对于Handle类的操作,我们要在Handle类进行复制的时候,累加Handle指向的UPoint的计数值

 

即复制构造函数以及赋值函数

 

1 Handle::Handle(const Handle &h)  2     :up(h.up)  3 {
4 ++up->u; 5 } 6 7 Handle& Handle::operator=(const Handle &h) 8 {
9 ++h.up->u; 10 if (--up->u == 0) 11 delete up; 12 up = h.up; 13 return *this; 14 }

 

而对于析构函数,则是减小引用计数,如果减到0了,就说明没有其他的Handle指向了UPoint,因此我们要删除掉.

 

1 Handle::~Handle() 2 {
3 if (--up->u == 0) 4 delete up; 5 }

 

剩下的就是定义Handle对于Point的操作了.即Handle::x(int xv)和Handle::(int yv)了.

 

这里有2种写法.

 

一种是像指针一样,对于赋值,就直接修改指向的Point里面的值.这种方法有一个问题,即所以都指向这个Point的Handle类获取的x值都会变化.

 

代码:

 

1 //point like  2 Handle& Handle::x(int xv)  3 {
4 up->p.x(xv); 5 return *this; 6 } 7 //point like 8 Handle& Handle::y(int yv) 9 {
10 up->p.y(yv); 11 return *this; 12 }

 

 

 

还有一种是写时复制技术,即每次对于共享的Point进行修改的时候都复制一份新的Point,然后进行修改.

 

这种技术在Handle中大量采用.在stl中,string也采用了同样的方法.

 

其额外开销很小,而效率也不差.

 

代码:

 

1 void Handle::allocup()  2 {
3 if (up->u != 1) 4 {
5 --up->u; 6 up = new UPoint(up->p); 7 } 8 } 9 10 Handle& Handle::x(int xv) 11 {
12 allocup(); 13 up->p.x(xv); 14 return *this; 15 } 16 17 Handle& Handle::y(int yv) 18 {
19 allocup(); 20 up->p.y(yv); 21 return *this; 22 }

 

至此,Handle类的第一部分就讲完了.

 

之后会有第二部分的讲解.解决了多出了一个UPoint的麻烦.

 

在前文handle part1的部分我讲了一种handle的设计方法。但是那个handle有一个缺点,就是必须要分离出来一个UPoint类做为计数,十分的不方便。这意味着每个类都要单独的出来一个UPoint。而这个UPoint仅仅只是作为计数作用。

在本文中,我们将要设计一种新的写法,利用UseCount类来为Handle所指的对象计数,同时减少UPoint类,同时简化Handle的写法。

 

在前文中,我们知道了Handle类需要计数。通过计数和指针来完成减少复制,达到优化的目的。

上文的写法:

1 class Point  2 {
/*{
{
{
*/ 3 public: 4 Point():_x(0),_y(0){} 5 Point(int x,int y):_x(x),_y(y){} 6 int x()const {
return _x;} 7 void x(int xv) { _x = xv;} 8 int y()const { return _y;} 9 void y(int yv) { _y = yv;} 10 private: 11 int _x; 12 int _y; 13 };/*}}}*/ 14 15 16 //all member is private..only assess by Handle 17 class UPoint 18 {
/*{
{
{
*/ 19 friend class Handle; 20 21 Point p; 22 int u;//count 23 24 //省略 25 };/*}}}*/ 26 27 class Handle 28 {
29 public: 30 //省略 31 32 private: 33 UPoint *up; 34 };

可以看到,计数是由UPoint来实现的.由Handle来管理.

但做为编写者的我们肯定是不愿意这么写的.这意味着每写一个Handle都得写一个这样的Uxxxx.实在是费事.

因此,我们可以很直观的想到一个写法,即在Handle里面指向的是Point *p和int *count;

这样就没有这个麻烦了.

当然这是一个很好的解决方案.

这里我再提出一种方案,即抽象引用计数,增加一个UseCount类.

通过这个类来计数.其是通用的.所有的Handle都可以使用.其次,这可以减少我们的指针操作.

实例如下:

被Handle管理的类的声明:

1 class Point  2 {
/*{
{
{
*/ 3 public: 4 Point():_x(0),_y(0){} 5 Point(int x,int y):_x(x),_y(y){} 6 int x()const {
return _x;} 7 void x(int xv) { _x = xv;} 8 int y()const { return _y;} 9 void y(int yv) { _y = yv;} 10 private: 11 int _x; 12 int _y; 13 };/*}}}*/

这个和原来的是一样的.

再来看看我们的UseCount

1 class UseCount  2 {
3 public: 4 UseCount():count(new int(1)){} 5 UseCount(const UseCount& uc):count(uc.count){ ++*count;} 6 UseCount& operator=(const UseCount &u); 7 ~UseCount(); 8 bool isonly() { return *count == 1;}//判断是否只指向一个计数,用于判断是否要删除 9 bool reattach(const UseCount &u);//重新连接,用于复制 10 bool makeonly();//分配一个新的,用于写时复制技术 11 private: 12 int *count;//计数 13 }; 14 15 UseCount& UseCount::operator=(const UseCount &u) 16 {
17 reattach(u); 18 return *this; 19 } 20 21 UseCount::~UseCount() 22 {
23 if (--*count == 0) 24 delete count; 25 } 26 bool UseCount::reattach(const UseCount &u) 27 {
28 29 ++*u.count; 30 if (-- *u.count == 0) 31 {
32 delete count; 33 count = u.count; 34 return true; 35 } 36 count = u.count; 37 return false; 38 } 39 40 bool UseCount::makeonly() 41 {
42 if (*count == 1) 43 return false; 44 --*count; 45 count = new int(1); 46 return true; 47 }

这个类实现了计数的功能.其内部有一个count的int 指针.在进行UseCount的复制的时候仅仅是++use.

其他的功能函数我也加了注释.大家应该能看懂.

剩下的就是我们的使用UseCount的Handle类了.

1 class Handle  2 {
3 public: 4 Handle():p(new Point){} 5 Handle(int x,int y):p(new Point(x,y)){}//使用了UseCount使得我们的构造函数显得异常简单, 6 Handle(const Point&pp):p(new Point(pp)){}//仅仅是分配了Point的空间而已 7 Handle(const Handle &h):p(h.p),count(h.count){};//复制构造函数也很简单.其实可以省略,也不会出错 8 ~Handle(); 9 Handle& operator=(const Handle &h); 10 int x() const{ return p->x(); } 11 int y() const{ return p->y(); } 12 Handle& x(int); 13 Handle& y(int); 14 15 16 private: 17 Point *p;//被Handle的对象 18 UseCount count;//使用UseCount 19 };

我们来看看各个函数

析构函数:

1 Handle::~Handle() 2 {
3 if (count.isonly()) 4 delete p; 5 6 }

这个析构函数判断了p是否只指向了一个对象,如果是的话,因为要析构,所以p也要相应的被删除

=操作符:

1 Handle& Handle::operator=(const Handle &h) 2 {
3 if (count.reattach(h.count)) 4 delete p; 5 p = h.p; 6 return *this; 7 }

reattach函数返回bool指,如果count == 1的话,返回值为true,那就要把p给删除.

采用了写时复制技术的x(int),y(int)

1 Handle& Handle::x(int x0)  2 {
3 if (count.makeonly()) 4 p = new Point(*p); 5 p->x(x0); 6 return *this; 7 } 8 9 Handle& Handle::y(int y0) 10 {
11 if (count.makeonly()) 12 p = new Point(*p); 13 p->y(y0); 14 return *this; 15 16 }

makeonly()使UseCount分配一个新的计数值,返回true说明需要分配,false则代表其本身就只指向了一个对象,不需要重新分配.

最后是咱们的测试main函数

1 int main()  2 {
3 Handle h(10,10); 4 cout << h.x() << " " << h.y() << endl; 5 h.x(20); 6 cout << h.x() << " " << h.y() << endl; 7 Handle h2(h); 8 cout << h2.x() << " " << h2.y() << endl; 9 return 0; 10 }

总结:

      我们可以看到,通过抽象计数对象,我们的Handle的操作显得特别简单,减少了很多的指针操作.同时也减少了出错的可能.

      而通过这个方法,我们也减少了一个UPoint类的书写.简化了操作.

  Handle类有智能指针一说.其行为有点像指针.同时又省却了我们指针操作的麻烦.而且,减少了很多复制的操作.效率和代码的健壮性得到了提高.

       至此,handle(句柄类)的讲解就结束了.

 

转载于:https://www.cnblogs.com/zhangjing0502/archive/2012/02/28/2371255.html

你可能感兴趣的文章
程序员笔记|循序渐进解读Oracle AWR性能分析报告
查看>>
UniDAC使用教程(一):连接到数据库
查看>>
h3c s5820交换机_简单配置
查看>>
Nagios开发邮件报警程序
查看>>
memcached 和 mysql 结合使用的两种实现选择?
查看>>
Blog被“挂广告”的来龙去脉——家用路由器的安全问题
查看>>
Flex调用WebService的方法
查看>>
如何把FTP用户帐号存放进MariaDB数据库中
查看>>
Linux下安装apache可能遇到的问题总结
查看>>
jdk8.0 内存划分
查看>>
我的友情链接
查看>>
字符编码的演变:UTF-8中文占几个字节
查看>>
Flume
查看>>
php instanceof
查看>>
Android第十四天
查看>>
drupal 后台路径
查看>>
移动安全新时代 Chinasec起名赢iPad2
查看>>
V2X项目小结
查看>>
学习笔记---乐观锁 悲观锁 死锁
查看>>
如何避免windows电脑假死机
查看>>