GEMINIGHT 警告:您的浏览器不支持JavaScript将无法正常浏览!
Warning: Your browser does not support JavaScript!
📋注册(Register) | 📛登录(Login)
🎲

主站(Home) »  论坛(Forum)  » 程序编写(Program)
swsys
注册于:2005年8月26日
等级:注册会员
帖子数:7
积分:110
阅读权限:20
[推荐]C++代理类! 1樓
Tags: C++

Tags引力关联贴

疑问:想在一个容器中存储类型不同但相互关联的对象.

\N

: 这种需要为每个对象创建一个,并要将存储在容器中.创建将会复制所的对象,就像复制一样.

\N

话题:

\N

引入问题:

\N

\N

问题:假设有一个表示不同种类的交通工具的类派生层次:

\N

class Vehicle{

\N

public:

\N

virtual double weight ( ) const =0 ;

\N

virtual void start ( ) = 0 ;

\N

};

\N

class RoadVehicle: public Vehicle { …….. };

\N

class AutoVehicle: public RoadVehicle { ……. };

\N

class Aircraft: public Vehicle { …………. };

\N

class Helicopter: public Aircraft { ……….. };

\N

\N

所有Vehicle都有一些类Vehicle中的成员声明的共有属性.

\N

\N

下面假设我们要跟踪处理一系列不同种类的Vehicle.在实际中,我们可能会使用某种容器类;然而,为了使表述更简洁,我们使用数组来实现.这样的话,就试一试

\N

Vehicle parking_lot[1000];

\N

没有产生预期的效果,为什么?

\N

\N

表面上看是由于Vehicle是一个抽象基类,因为成员函数weight start都是纯虚函数.通过在声明中写=0,明确声明了这些函数可以空着不定义.因此,只有从类Vehicle派生出来的类才能够实例化,Vehicle本身不会有对象.既然Vehicle对象不存在,当然也就不可能有其对象数组了.

\N

\N

我们的失败还有更深层次的原因.如果我们思考一下,假设存在类Vehicle 的对象会出现什么样的情况,原因就会更加明显.譬如,假设我们剔除了类Vehicle中的所有纯虚函数.如果我们写类似于下面的语句,会有什么结果呢?

\N

Automobile x=/*…………..*/

\N

Parking_lot[num_Vehicles++] = x;

\N

\N

答案是:x赋给parking_log的元素,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员.该赋值语句还会把这个被剪裁了的对象复制到parking_lot数组中去.

\N

\N

这样,我们就只能说parking_log是Vehicle的集合,而不是所有继承自Vehicle的对象的集合.明显,这是我们不希望看到的结果.

\N

经曲解决方案:

\N

\N

这种情况下,实现灵活性的常见做法是提供一个间接层,最早的合适的间接层形式就是存储指针,而不是对象本象:

\N

Vehicle* praking_lot[1000];

\N

然后,输入类似

\N

Automobile x=/* …. */;

\N

Parking_lot[num_vehicles++] = &x;

\N

的语句.这种方解决了追切的问题,但也带来了两个新问题.

\N

\N

首先,我们存储在parking_lot中的是指向x的指针,在本例中是一个局部变量.这样,一旦变量x没有了,parking_lot就不知道指向什么东西.

\N

\N

我们可以这么变通一下,放入parking_lot中的值,不是指向原对象的指针,而是指向它们的副本的指针.然后,可以采用一个约定,就是当我们释放parking_lot时,也释放其中所指向的全部对象.因此,可以把前面的例子改为:

\N

Automobile x=/*…*/;

\N

Parking_log[num_vehilcles++]=new Automobile(x);

\N

\N

尽管这样修改可以不用存储指向本地对象的指针,它也带来了动态内存管理的负担.

\N

🗓2005-8-26 03:27(约19年前)  👁821
swsys
注册于:2005年8月26日
等级:注册会员
帖子数:7
积分:110
阅读权限:20
2樓

虚复制函数:

\N

\N

让我们想一个办法来复制编译时类型型未知的对象.我们知道,C++中处理未知类型的对象的方法就是使用虚函数.这种函数的一个显而易见的名字就是copy,clone也可以,不过稍微有点似是而非.

\N

\N

由于我们是想能够复制任何类型的Vehicle,所以应该在Vehicle类中增加一个合适的纯虚函数:

\N

class Vehicle{

\N

public:

\N

virtual double weight ( ) const =0 ;

\N

virtual void start ( ) = 0 ;

\N

virtual Vehicle* copy() const = 0;

\N

};

\N

\N

接下来,在每个派生自Vehicle的类中添加一个新的成员函数copy.指导思想就是,如果vp的指向某个继承自Vehicle的不确定类的对象,vp->copy()会获得一个指针,该指针指向该对象的一个新建的副本.例如,如果Truck继承类Vehicle,则它的copy函数就类似于:

\N

Vehicle* Truck::copy() const

\N

{

\N

Return new Truck(*this);

\N

}

\N

\N

当然,处理完一个对象后,需要清除该对象.要做到这一点,就必须确保类Vehicle有一个虚析构函数.

\N

\N

\N

\N

定义:

\N

\N

我们已经理解了根据需要复制对象的方法.现在,来看看内存分配.有没有一种方法既能使我们避免显式地处理内存分配,又能保持类Vehicle在运行时绑定的属性呢?

\N

\N

解决这个问题的关键是要用类来表示概念,这在C++中是很常见的.我总是把这一点当作最基本的C++设计原则.在复制对象的过程中运用这个设计原则,就晃定义一个行为和Vehicle对象相似.而又潜在地表示了所有继承自Vehicle类的对象的东西.我们把这种类的对象叫做.

\N

\N

每个Vehicle都代表某个继承自Vehicle类的对象,只要该关联着这个对象,该对象就肯定存在.因此,复制就会复制相对应的对象,而给赋值也会先删除旧对象.再复制新对象.

\N

Class VehicleSurrogate

\N

{

\N

Public:

\N

VehicleSurrogate();

\N

VehicleSurrogate(const Vehicle&);

\N

~VehicleSurrogate();

\N

VehicleSurrogate(const VehicleSurrogate&);

\N

VehicleSurrogate& operator=(const VehicleSurrogate&);

\N

Private:

\N

Vehicle* vp;

\N

};

\N

\N

上述类有一个以const Vehicle&为参数的构造函数,这样就能为任意继承自Vehicle的类的对象创建.同时,类还有一个缺省构造函数,所以我们能够创建VehicleSurrogate对象的数组.

\N

\N

然而,缺省构造函数也给我们带来了问题:如果Vehicle是个抽象基类,我们应该如果规定VehicleSurrogate的缺省操作?它所指向的对象的类型是什么?不可能是vehicle,因为根本就没有vehicle对象.

\N

\N

为了得到一个更好的方法,我们要引入行为类似于零指针的空的概念.能够创建.销毁和复制这样的,但是进行其他操作就视为出错.

\N

\N

到目前为止,不再需要任何其他的操作了,这就使我们能很容易地写出成员函数的定义:

\N

\N

VehicleSurrogate:: VehicleSurrogate():vp(0){ }

\N

VehicleSurrogate:: VehicleSurrogate(const Vehicle& v):vp(v.copy()){ }

\N

VehicleSurrogate:: ~VehicleSurrogate()

\N

{

\N

delete vp;

\N

}

\N

VehicleSurrogate:: VehicleSurrogate(const VehicleSurrogate& v)

\N

:vp(v.vp? v.vp->copy() : 0 ) { }

\N

VehicleSurrogate:: operator=(const VehicleSurrogate& v)

\N

{

\N

If(this!=&v)

\N

{

\N

delete vp;

\N

vp=(v.vp? v.vp->copy() : 0 );

\N

}

\N

Return * this;

\N

}

\N

这里有3个技巧值得我们注意.

\N

\N

首先,注意每次对copy的调用都是一个虚拟设用.这些调用只能是虚拟的,别无选择,因为类vehicle的对象不存在.即使是那个只接收一个const vehicle&参数的复制构造函数中,它所进行的v.copy调用也是一次虚拟调用,因为v是一个引用而不是一个普通对象.

\N

\N

其次,注意前于复制构造函数和赋值操作符中的v.vp非零的检测,这个检测是必需的,否则调用v..vp->copy时就会出错.

\N

\N

再次,注意对赋值操作符进行检测,确保没有将赋值经它自身.

\N

\N

下面剩下的工作只是令该类支持类vehicle所能支持的其它操作了.在前面的例子中,weihtstart,所以要把它们加入到类VehicleSurrogate:

\N

Class VehicleSurrogate

\N

{

\N

Public:

\N

VehicleSurrogate();

\N

VehicleSurrogate(const Vehicle&);

\N

~VehicleSurrogate();

\N

VehicleSurrogate(const VehicleSurrogate&);

\N

VehicleSurrogate& operator=(const VehicleSurrogate&);

\N

Double weight() const;

\N

Void start();

\N

Private:

\N

Vehicle* vp;

\N

};

\N

\N

注意这些函数都不是虚拟的,我们这里所使用的对象都是类VehicleSurrogater 的对象,没有继承自该类的对象.当然,函数本身可以调用相应vehicle对象中的虚函数.它们也应该检查确保vp不为零.

\N

\N

\N

\N

Double VehicleSurrogate::weight() const

\N

{

\N

If(vp= =0)

\N

Thorw *empty VehicleSurrogate.weight()*;

\N

Return vp->weight();

\N

}

\N

\N

void VehicleSurrogate::start()

\N

{

\N

If(vp= =0)

\N

Thorw *empty VehicleSurrogate.start()*;

\N

Return vp->start();

\N

}

\N

一旦我们完成了所有这些工作,就很容易定义我们的停车场(parking_lot):

\N

VehicleSurrogate parking_lot[1000];

\N

Automobile x;

\N

Parking_lot[num_vehicles++]=x;

\N

最后一条语句等价于:

\N

Parking_lot[num_vehicles++] = VehicleSurrogate(x);

\N

\N

这个语句创建了一个关于对象x的副本,并将VehicleSurrogate对象绑定到该副本,然后将这个对象赋值给parking_lot的一个元素.当最后销毁parking_lot数组时,所有这些副本也将被清除.

\N

\N

小结:

\N

将继承和容器共用,迫使我们要处理两个问题:控制内存分配和把不同类型的对象放入同一个容器中.采用基础的C++技术,用类来表示概念,我们可以同时兼顾这两个问题..做这些事情的时候,我们提出一个名叫类的类,这个类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象.通过在容器中用对象而不是对象本身的方式,解决了我们的问题.
🗓2005-8-26 03:29(约19年前)
swsys
注册于:2005年8月26日
等级:注册会员
帖子数:7
积分:110
阅读权限:20
3樓

希望能够帮助到你们....

🗓2005-8-26 03:29(约19年前)

标题(Title):
关键字标签(Tags):
路人:回贴可以不必登录