1.代理类
代理类是用来干什么的?
解决将继承体系中的对象放到同一个容器中的问题,这就必须解决两个子问题:1.编译时类型未知类型的绑定;2.合理的分配内存。
用交通工具类来举列子,现在有这样的一个类:
//交通工具类1
class Vehicle {
public:
double weight() const;
void start();
// ......
};
//各种类型的交通工具
class RoadVehicle : public Vehicle { /* ...... */ };
class AutoVehicle : public RoadVehicle { /* ...... */ };
class Aircraft : public Vehicle { /* ...... */ };
class Helicopter : public Aircraft { /* ...... */ };
现在我们想定义一个类,可以存放各种交通工具类的指针
Vehicle *parking_lot[1000]; // 指针数组
Helicopter x;
parking_lot[num_vehicles++]=new Helicopter(x);
但是假如我们不知道x是什么类型,可以这样解决,修改Vehicle 类如下:
//交通工具类2
class Vehicle {
public:
double weight() const;
void start();
Vehicle *copy() const;
// ......
};
这样每个具体的交通工具类实现一个copy()接口,以Helicopter类为例,说明copy()的作用
Vehicle *Helicopter::copy() const
{
//为避免悬垂指针危险,这里应该拷贝一份Helicopter类
return new Helicopter(*this);
}
接下来这样操作,将未知类型的x放入parking_lot中
parking_lot[num_vehicles++] = x.copy();
这样,是不是就实现了一个对象指针数组(首先是一个数组,数组元素是对象指针,指向各种交通工具),存放各种交通工具(无论是否已知该交通工具的类型,因为每个交通工具类中都必须实现一个copy()接口,来拷贝自己,然后将这个对象的指针返回)
那么,问题又来了,我们知道,定义一个交通工具类的时候,通常是将其中的方法定义为纯虚函数,形成一个抽象类,但是,抽象类是不能实例化的;此外,还有一个问题,就是内存管理,因为我需要释放parking_lot数组中所指向的每个类。
此时就出现了代理类这个概念,先看代码,在做解析:
class Vehicle {
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual Vehicle *copy() const = 0;
// ......
};
class VechicleProxy {
public:
VechicleProxy();
VechicleProxy(const Vehicle &);
~VechicleProxy();
VechicleProxy(const VechicleProxy &);
VechicleProxy &operator=(const VechicleProxy &);
private:
Vehicle *p;
};
VechicleProxy::VechicleProxy(): p(0) { }
VechicleProxy::VechicleProxy(const Vehicle &BigStar): p(BigStar.copy()) {}
VechicleProxy::~VechicleProxy() { delete p; }
VechicleProxy::VechicleProxy(const VechicleProxy &v): p(v.p ? v.p->copy() : 0) {}
VechicleProxy::operator=(const VechicleProxy &v)
{
if (this != &v)
{
delete p;
p = (v.p ? v.p->copy() : 0);
}
return *this;
}
在代理类VechicleProxy中,只有一个Vehicle *类型的成员变量,这样可以实现指向不同的交通工具,并且实现了内存清理工作,又解决了抽象类Vechicle不能实例化的问题。
现在,可以这样使用代理类
VehicleProxy parking_lot[1000]; Helicopter x; parking_lot[num_vehicles++] = x;
总结:
代理类解决了这样的问题:内存的分配和编译时类型未知对象的绑定。
2.句柄类
在代理类中存在两个问题,第一,通常是不允许对类添加一个copy函数,因为这样就修改了这个类,第二,当一个类非常大时,copy函数中实现对这个类的拷贝开销是非常大的。
句柄类是用来干什么的?
1.能够在运行时绑定未知类型的类及其继承类
2.管理内存分配和释放问题
3.避免代理类每次复制时都要拷贝对象的操作
现在,以平面坐标系上的点作为基类来理解句柄类的概念
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 &x(int);
~Handle();
private:
Point *p;
}
现在,我们的目的是要设计出一种方法,能够让我不必去复制对象,同时能够达到运行时绑定对象的方法,通俗的说也就是让AA、BB、CC等类都指向基类Point。解决的方法就是定义句柄(也称 智能指针),上面的handle就是句柄的一个雏形,让多个句柄指向同一个基类副本,只有当第一次给handle中p赋值时才会产生Point的唯一副本。此时,出现了一个问题,当有许多句柄指向一个基类副本的时候,怎样释放这个副本的内存呢?
此时就需要用到“引用计数”,记录一共有几个句柄绑定到了基类Point上,只有当数目为0的时候,才能删除掉这个副本,现在完善Handle代码如下:
//句柄类
class handle {
public:
Handle();
Handle(int, int);
Handle(const Point &);
Handle(const Handle &);
Handle &operator=(const Handle &);
Handle &x(int);
~Handle();
private:
Point *p;
int *u;
}
//当第一次实例化句柄类的时候,产生Point的副本,引用计数值为1
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(const Handle &h) : u(h.u), p(h.p) { ++*u; }
Handle & Handle::operator=(const handle &h)
{
++*h.u;
if (--*u == 0)
{
delete u;
delete p;
}
u = h.u;
p = h.p;
return *this;
}
Handle::~Handle()
{
if (--*u == 0)//只有当引用计数为0的时候,删除副本
{
delete u;
delete p;
}
}
细心的读者会发现,我并没有实现Handle &x(int);的函数体,因为这里涉及到一个问题,如下:
// 新的句柄h绑定到Point对象,产生Point的副本,xval为3,yval为4
Handle h(3, 4);
// 调用拷贝构造函数,h2也绑定到Point对象
Handle h2 = h;
//这行代码就会产生歧义,这个5是赋值给哪个句柄的xval?
h2.x(5);
// 这行代码也会产生歧义,n的值为3还是5?
int n = h.(x);
所以,句柄应该是值语义还是指针语义?如果是指针语义,就不能对对象的内容进行修改,如果修改了,那修改者应该清楚自己是想对原副本进行修改。如果是值语义,那就需要引入“写时拷贝”技术,只有当需要对该句柄值修改时,才会重新拷贝一份副本保存在自己的内存中,而不是去修改原副本。针对两种语义,分别实现Handle &x(int);
//指针语义
Handle &Handle::x(int x0)
{
p->x(x0);
return *this;
}
Handle &Handle::x(int x0)
{
//因为是值语义,需要将引用计数减1后,产生一个新的副本
//若此时刚好只有一个引用计数,那就可以直接修改原副本了
if (*u != 1)
{
--*u;
p = new Point(*p);
}
p->x(x0);
return *this;
}
此外,还可以在handle类中实现->等操作符的重载,使handle像指针一样使用去调用Point的成员函数。
以上只是说明了句柄类中的第2和第3个问题,至于第1个问题,和代理类类似,具体可以阅读这篇博客https://www.cnblogs.com/monicalee/p/3861336.html