当我们使用容器(或者数组)保存因继承关系而相互关联的对象时,就会遇到问题,因为对象不是多态的。
例如有下列表示交通工具的类
class Vehicle{
public:
virtual double weight() const=0;
virtual void start()=0;
};
class RoadVehicle: public Vehicle{/*.....*/};
class AutoVehicle: public RoadVehicle{/*.....*/};
class Aircraft: public Vehicle{/*.....*/};
class Helicopter: public Aircraft{/*.....*/};
这些类都是Vehicle的派生类。在实际应用中,我们可能需要一个容器或数组来存放这些类。例如:
Vehicle parking_lot[1000]来存放这些类。
因为基类Vehicle里面有纯虚函数,是抽象类,不可以被实例化,这样声明是错误的。但是假设声明正确,又会产生什么样的效果呢?
Parking_lot数组存放的是Vehicle基类对象,将派生类对象赋值给基类对象时,派生类对象将被切割掉一部分(切割掉派生类的部分),转换成基类。
考虑到基类指针可以指向派生类,于是有了下面一种解决方案。
提供一个间接层
数组存放的不是对象,而是指针。
Vehicle *parking_lot[1000]声明指针数组。
当赋值时
AutoVehicle x;
parking_lot[num++]=&x;
这样看起来解决了问题,但是又带来了新的问题。
首先x是一个局部对象,存储在栈上,出了定义域范围会自动释放,释放后parking_lot指向什么东西我们并不知道。
那么这样变通,parking_lot不指向x,而是创建一个副本,然后指向这个副本
parking_lot[num++]=new AutoVehicle(x);
当我们释放parking_lot数组时,释放其指向对象的副本。
这样做增加了内存开销。但是还有一个前提,就是要知道对象x的类型。假如并不知道x的类型,例如把parking_lot[a]赋值给parking_lot[b],由于不知道parking_lot[a]的类型,无法创建新的副本。
于是就有了虚复制函数。
虚复制函数
虚复制函数就是复制编译时类型位置的对象。用一个虚函数来实现
class Vehicle{
public:
virtual double weight() const=0;
virtual void start()=0;
virtual Vehicle* copy()const=0;
};
在派生类中实现
class RoadVehicle: public Vehicle{
/*.....*/
Vehicle *copy()const
{
return new RoadVehicle(*this);
}
};
对于虚函数,在派生类中返回类型必须与基类的返回类型完全匹配,但是有个例外,就是虚函数基类实例返回类类型的引用或指针,则该虚函数的派生类实例可以返回基类实例返回类型的派生类(指针或者引用)。
现在已经有了运行时复制对象的方法,那么现在可不可以找到一种方法,既可以避免动态分配内存,又能保持多态的属性呢?那么就是使用代理类。
代理类
代理类是定义一个类,来管理基类指针。它的行为和基类指针类似,它可以管理基类以及基类派生的对象。
它的成员变量为基类指针。成员函数为构造函数、析构函数、重载=运算符、基类里面的虚函数。
class VehicleSurrogate{
public:
VehicleSurrogate();
VehicleSurrogate(const Vehicle&);
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operator=(const VehicleSurrogate&);
double weight() const;
void start();
~VehicleSurrogate();
private:
Vehicle *vp;
};
默认构造函数是为了创建空的代理
VehicleSurrogate::VehicleSurrogate():vp(0){}
VehicleSurrogate::VehicleSurrogate(const Vehicle& v):vp(v.copy()){}
//在调用copy函数之前,要先检查vp是不是空指针。
VehicleSurrogate::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);//检查vp是不是空指针
}
return *this;
}
之后就是为每个虚函数在代理类中创建一个方法。
double VehicleSurrogate::weight()const
{
if(vp==0)
throw "enpty VehicleSurrogate.weight()";
return vp->weight();
}
void VehicleSurrogate::start()
{
if(vp==0)
throw "enpty VehicleSurrogate.start()";
return vp->weight();
}
代理类为每个管理的对象创建了副本,浪费了内存。一种可以共享内存存储对象的方法是智能指针。