先看一个交通工具的类派生层次:
class Vehicle
{
public:
virtual double weight() const = 0;
virtual void start() = 0;
};
class RoadVehicle : public Vehicle
{
};
class AutoVehicle : public RoadVehicle
{
};
假设我们要跟踪处理一系列不同类型种类的Vehicle。实际中要用到某种容器类,这里用数组来实现。
如果直接用Vehicle parking_lot[1000];这样肯定是不行的,因为抽象类是不能被创建的。
经典的解决方案是提供一个间接层,OK就是指针
Vehicle* parking_lot[1000];
然后输入类似
AutoVehicle x = /* ... */;
Parking_lot[num_Vehicles++] = &x;
这种方式是有问题的,容器中存储的是局部变量,当超过局部变量作用域的时候,指针就失效了。
尝试用下面这种方法
Parking_lot[num_Vehicles++] = new AutoVehicle;
当我们想要让Parking_lot[p]创建一个和Parking_lot[q]一样的类型时,我们该怎么做呢,要在抽象类Vehicle中添加copy纯虚函数。
class Vehicle
{
public:
virtual double weight() const = 0;
virtual void start() = 0;
//复制
virtual Vehicle* copy() const = 0;
};
派生类中要类似这样实现
Vehicle* AutoVehicle::copy() const
{
//使用系统默认的复制构造函数
return new AutoVehicle(*this);
}
清除对象要添加一个虚析构函数了。
如果用代理实现的话
class VehicleSurrogate
{
public:
VehicleSurrogate():vp(0){}
VehicleSurrogate(const Vehicle& v):vp(v.copy()){}
~VehicleSurrogate(){delete vp;}
VehicleSurrogate(const VehicleSurrogate& v)
:vp(v.vp?v.vp->copy():0){}
VehicleSurrogate& operator=(const VehicleSurrogate& v)
{
if(this != &v)
{
delete vp;
vp = (v.vp?v.vp->copy():0);
}
return *this;
}
double weight() const
{
if(!vp)
Throw “ empty”;
return vp->weight();
}
void start() const
{
if(!vp)
Throw “empty”;
vp->start();
}
private:
Vehicle* vp;
};
此时就不需要指针了
VehicleSurrogate parking_lot[1000];
AutoVehicle x;
Parking_lot[num_vehicles++] = x;
//最后一行相当于 = VehicleSurrogate(x);
关于内存的管理直接被封装到代理类中。
如果想避免复制所代理的对象,就要考虑用到句柄类(或者称为智能指针)了。
句柄类主要是避免复制对象,尤其对象是一种不能轻易被复制的资源时,例如文件。
两种实现方式,一种是数据和引用计数绑定在一起,另外一种是分开的。
先说第一种:
//数据类
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 _x){xval = _x;}
point& y(int _y){yval = _y;}
private:
int xval,yval;
};
//引用计数
class Handle;
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)){}
Handle(const point& p):up(new UPoint(p){}
//我们设计复制为指针语义,非值语义
Handle(const Handle& h):up(h.up){++up->u;}
Handle& operator=(const Handle& h)
{
++h.up->u;
if (--up->u == 0)
{
delete up;
}
up = h.up;
return *this;
}
~Handle(){if (!--up->u) delete up;}
int x() const{return up->p.x();}
int y() const{return up->p.y();}
Handle& x(int _x)
{
//指针语义
up->p.x(_x);
return *this;
//值语义(保证要修改的对象不能被其他句柄引用,需要查看引用计数,如果不为1,就需要复制了)
if (up->u != 1)
{
--up->u;
up = new UPoint(up->p);
}
up->p.x(_x);
return *this;
}
Handle& y(int _y)
{
//指针语义
up->p.y(_y);
return *this;
//值语义(
if (up->u != 1)
{
//只有在必要的时候,才进行复制,这就是写时复制
--up->u;
up = new UPoint(up->p);
}
up->p.x(_x);
return *this;
}
private:
UPoint *up;
};
Handle h(3,4);
Handle h2 = h;
h2.x(5);
Int n = h.x();
此时n的值是3还是5呢?
低层是h2和h都指向同一数据(3,4),如果是指针语义,那n的值就是5了,如果是值语义,保证修改h2的值不会影响到h则仍是3。
第二种则是计数和数据分离(也就是用另外一个类专门管理引用计数,而句柄类中则同时存在数据和引用计数类),在这里第二种就不实现了。