泛型编程在Delphi中的实现(200分)

  • 主题发起人 左轻侯
  • 开始时间

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
泛型编程(Generic Programming)号称编程思想的又一次革命
但一直都是用C++实现的,如何在Delphi中实现呢?
我一直在考虑这个问题
发表有价值意见的有分
 

房客

Unregistered / Unconfirmed
GUEST, unregistred user!
要说RAD DELPHI当之无愧
但DELPHI 无法建立自己的 STL 。。。
 

房客

Unregistered / Unconfirmed
GUEST, unregistred user!
推荐个地方 权当抛“砖”了:)
http://icl.pku.edu.cn/bswen/
 

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
Delphi对ADTs的实现,不需要通过STL,应该可以用它自己的方式
但是好象没有人做过系统的讨论
 

小猪

Unregistered / Unconfirmed
GUEST, unregistred user!
能否把你们手上的资料跑出来看看先?
 
C

cheka

Unregistered / Unconfirmed
GUEST, unregistred user!

对范型了解得不多,有个疑问,如果实现了像Java(还有VCL)那样的
单根继承,还需要有所谓的范型么? (当然在C++里利用模板机制还能
解决一般数据类型的问题。暂不考虑)
 
M

maming

Unregistered / Unconfirmed
GUEST, unregistred user!
向高人学习。
 

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
cheka:
你想说的也正是我想说的
我觉得在单根结构中实现GP,应该比STL更加先进
但不可否认的是STL现在已经是一个相当完善的体系,但我们说的这种方式
却几乎没有什么尝试
很奇怪的事情,不知道我的理解哪里出了问题
 
A

ahxia

Unregistered / Unconfirmed
GUEST, unregistred user!
D

deadcandance

Unregistered / Unconfirmed
GUEST, unregistred user!
A

atorm

Unregistered / Unconfirmed
GUEST, unregistred user!
以左兄为榜样,我也正在努力学习。努力!奋斗!
 
C

cheka

Unregistered / Unconfirmed
GUEST, unregistred user!
今天想到的,Delphi和Java中很难实现像 C++ 中以模板为基础的那种泛型

1. 基本数据类型的问题没法解决,这问题很让人头痛,而且不能像我上面所说的
那样“暂不考虑”。

2. 即使对于对象而言,Delphi 和 Java 利用多态特型辅助RTTI来实现数据类型
参数化相对于模板机制来说,开销要大很多很多... ,同时,前者只有放到
运行期,而C++中的泛型则是编译期的。
 

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
不愧是cheka,一言说中要害:)
我倒觉得,运行期有运行期的好处,编译期有编译期的好处
过两天我把想法整理一下,写个详细点的东西发上来
 

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
据称:JAVA也将下一版本支持泛型技术
来源:http://www.csdn.net/news/newstopic/3/3376.shtml
不知它怎么支持的?有人知道吗?
 

左轻侯

Unregistered / Unconfirmed
GUEST, unregistred user!
泛型编程在非C++语言中的实现之探讨
左轻侯
2001.9.22

  GP(Generic Programming,泛型编程)号称编程思想的又一次革命。但是,在论述GP
的资料中,一般都是以C++语言为基础来讨论。那么,GP是否可以在其它的编程语言中实现
呢?这是作者一直在思考的一个问题,因为水平有限和资料匮乏,收获甚微。现将一些不成
熟的想法整理出来,请方家不吝指教。
  本文以Delphi为例(Java的情况与此类似,可参照),讨论GP的另一种实现思路。代
码是随手写出的,未经验证。
  根据作者的理解,实现GP的关键之处,在于实现ADT(Abstract Data Type,抽象数据
类型)。只有实现了ADT,才能够将具体的数据类型与通用的算法分离开来。
  在C++中,ADT的存储是通过模板来实现的。举一个最简单的栈的例子(没有给出实现部
分):
  
template <class Type> class Stack{
public:
void Push(const Type &amp;item);
Type Pop;
...
}

  栈的应用:
  
Stack <int> s;
int data;
data = 1;
s.Push(data)
//入栈

int out;
out = s.Pop
//出栈

  通过建立一个int类型的Stack对象,实现了对int类型数据的存储。
  但是,在Delphi/Java中,并没有模板这种机制。那么如何实现ADT呢?与C++不同的
是,Delphi/Java的类继承体系是单根结构的,也就是说,在Delphi中,所有的class都通
过编译器强制而保证成为TObject的子类(Java中是Object)。这个TObject可以看成是一
切类的最高层次的抽象。那么,是否可以认为Delphi/Java中已经先天地提供了对ADT的支
持呢?
  试着用这种思路建立一个栈:
  
TStack = class
public
procedure Push(item:TObject);
function Pop:TObject;
...
end;

  这个TStack类针对TObject类型的对象进行操作。但是,这个类还不能立即应用,因为
在Delphi中,简单数据类型并不是对象。所以必须再建立一个自定义的数据类型。下面建立
了只有一个integer成员的自定义数据类型:
  
TADT = class
public
data:integer;
end;
  
  再来看栈的应用:
  
var
stack:TStack;
adt1,adt2:TADT;
begin
stack := TStack.create;
adt1 := TADT.create;
stack.Push(adt1)
//入栈

adt2 := stack.Pop as TADT
//出栈

stack.free;

  这样就完成了对ADT对象的存储。必须注意到,在入栈时,是将adt对象直接入栈的,
因为TStack类是对TObject进行操作,由于Delphi的单根结构,可以将任何类型的对象赋
给TObject类型的变量。但是,出栈时,返回的也是一个TObject的变量,因此必须用as
完成一次向下映射。同样由于单根结构,Delphi/Java提供了对RTTI的强大支持,因此这种
向下映射是轻而易举的事情。但是,毫无疑问,RTTI在效率上有所损失。
  在实现了ADT的存储之后,如何才能实现对ADT的操作呢?
  在C++中,通过运算符重载来解决这个问题。看一个例子:  
  在一个List类中,执行查找操作:
  
template <class Type> class List{
Type* find(Type &amp;value)
//查找指定数据项,找到则返回其地址,否则返回NULL
...
}

template <class Type> Type* List<Type>::find(Type &amp;value){
Type* p = first->link;
where(p!=NULL&amp;&amp;!(p->data==value)){
p=p->link;
}
return p;
}

  在List类的find函数的实现中,代码抄自链表结构的实现,不需要关心其细节。需要
注意的地方只有一个,即判断条件中的p->data==value。由于p->data和value都是ADT,
在建立一个List类的对象时,它们可能是简单数据,也可以是任何的自定义数据类型。但是,
仍然可以统一使用==这样的运算符,对它们进行操作。为什么呢?原因在于运算符重载。
  下面用一个Point类来说明这一点:

class Point{
private:
int x,y;
public:
int operator ==(const Point &amp;point)
//判断两个Point对象是否相等
...
}

int Point::eek:perator ==(const Point &amp;point){
if(x==point.x&amp;&amp;y==poing.y){
return 1;
}else{
return 0;
};
}

  可以看到,由于重载了==运算符,两个Point对象之间可以进行比较。同理,任何数据
类型,只要重载了相应的运算符,就可以被容器类进行统一的操作。
  当目光重新转向非C++的时候,问题又出现了:Delphi/Java不支持运算符重载。做为补
救措施,可以用函数来代替运算符。例如,统一使用equals函数来代替==。可是,更大的问
题在于:容器类操作的对象是TObject,而TObject并没有equals这样的方法。(在Java中,
object有equals方法,但并没有其它的完整的运算方法。)
  在不改变Delphi/Java现有语法的前提下,作者能想到的解决办法是,建立一个TADT
类,作为所有数据结构的基类。TADT中定义了很多象equals、add之类的抽象方法。自定义
的数据类型一律从TADT派生,并重载相应方法。而容器类则只需要对TADT进行操作,即可
实现通用的算法了。但是,这种解决方法并不理想。
  (补充一点,其实Delphi自己提供了一些通用的容器类,如TList、TCollection、TStack、
TQueue等。但与本文中所说的不同,它们存储的不是TObject,而是pointer。由于Delphi
的“引用对象模型”机制,存储TObject对象,其实也就等于存储一个pointer,不同之处
在于,pointer不但可以存储对象,而且可以存储基本数据类型。这应该也是Borland如此
设计的原因。但是,这些容器类只提供了add、delete之类的管理方法,并没有提供通用的
算法,因为对pointer不能进行复杂的操作。实际操作中,往往是程序员从容器类派出一个
新类,或者在自己的类中维护一个容器类。这也是一种解决办法,但算法就不能独立出来了。)
  
  综上所述,作者的观点如下:
  1、C++中通过模板机制来实现ADT的存储,Delphi/Java同样可以通过单根结构+RTTI
的机制来实现。其不同之处在于,C++的实现是语法上的,而Delphi/Java是逻辑上的,也就
是说,C++是通过一套特殊的语法来实现的,而Delphi/Java是根据OOP的理论和本身的类库
体系自然地实现的。作者个人以为,从这个角度来看,Delphi/Java的实现机制更加简单和
直观。
  2、从运行效率来算,C++肯定要强于Delphi/Java,因为C++的实现是编译期的,而
Delphi/java是运行期的。RTTI的使用将对效率造成不小的影响。但是,从另一个角度来考
虑,ADT在运行期的实现也带来了好处,那就是可以在运行期随意地改变容器类所存储的数
据结构类型。作者还没有考虑过这种“数据类型的多态”会对编程带来什么实质性的后果。
不过大胆地设想一下,以后也许可以将标准算法封装在DLL之类已编译的模块中,甚至封装
在OS提供的COM对象里,程序员只需要创建自己的数据类型,将其提交给相应的接口?……
  3、C++中通过运算符重载,来实现对ADT的操作。在不支持运算符重载的Delphi/Java
中,作者未能发现好的代替方法。建立统一的ADT抽象类的解决方法,只能说是一个馊主
意。:-(有程序员倾向于Delphi中应该增加运算符重载,这应该是理由之一,不知道Borland
是否重视。另外,据说下一版的Java将会全面支持GP,不知道是通过什么机制来实现?请
了解内幕的高手介绍一下。
 
W

wsn

Unregistered / Unconfirmed
GUEST, unregistred user!
高深,没看懂[:(]
 
B

Boat

Unregistered / Unconfirmed
GUEST, unregistred user!
有点意思
 
M

mikedeakins

Unregistered / Unconfirmed
GUEST, unregistred user!
我个人认为,GP 有两个实现基础:多重继承 和 模板。
多重继承为一个对象提供在子类中扩充功能的能力。当然,单独继承也可以,但是无法实现
真正的设计精神。考虑这个案例:我们需要类具有“持久性”,即一个类可以把自己的敏感
数据写入永久存储设备,并且可以从中读出数据。这类似于 VCL 体系中的 TObject -
TPersistent 单继承结构,但是,这种单继承是很蠢的。因为如果一个新的类需要实现持久
功能,就必须从 TPersistent 或者子类继承,而另一方面,VCL 体系中的很多类并不需要
持久功能,这两方面造成了冗余代码和较低的效率。C++ 怎么做?
class CPersistent
{
virtual WriteToFile() = 0;
virtual ReadFromFile() = 0;
};
class CDummyWithoutPersistent
{
...........................
};
class CDummyWithPersistent : CDummyWithoutPersistent, CPersistent
{
...........................
};
这样做的好处是无需恐怖的树形继承结构。易于功能扩充。

而模板的用处也绝不只是省却了大量的函数名重载的工作量。如果一个类需要得到另一个
类型未知的类(比如适配子),你不用模板用什么?同时考虑效率和代码长度,只有模
板。由于模板在编译阶段已经被束定了,所以这种机制避免了树形类结构。

(要睡觉了,先写到这儿吧)
 
M

mikedeakins

Unregistered / Unconfirmed
GUEST, unregistred user!
(接着上面写)
泛型和生成式程序设计一般应用在一个程序的算法核心,因为这是程序的根本,支持了所有
程序能够实现的功能。对于这个核心中的基础算法,我不信有人能够抛开效率这个问题空谈
泛型(STL编译出的汇编代码和原生C语言编译出的汇编代码具有同样的效率)。
建议大家看一看《设计模式》这本书,这本书包括了几十个经典的设计模式,通读以后会
找到在效率、空间、可移植性、可扩展性的一个结合点。
 
D

dream_soft

Unregistered / Unconfirmed
GUEST, unregistred user!
方才在大富翁注册了自己的帐号,就看到左兄关于GP的文章,幸甚。

在我眼中的GP,应该是使设计好的类适用于不同数据类型的一种方式。
左兄所言,"C++中通过运算符重载,来实现对ADT的操作。"其实在STL中有所谓"约定"的概

念,即只要你的类设计满足"约定",实质上是实现了约定的一组接口,就可以为STL使用。例

如要求类实现对<的重载,实质上要求实现约定的函数。
在Delphi中不支持操作符重载,并未对语言的表达能力产生实质性的影响,即使用操作符重

载做到的,使用函数也同样可以作到,只是没那么美观而已。
可以说,不支持操作符重载,并不构成实现GP的障碍。
可以说左兄的方案,其实已经部分的实现了GP,至于基本数据类型的问题,可以如JAVA中一

样生成包装类来处理。但我个人看来,这种用RTTI实现的GP,有一本质的缺陷:类型安全性。
考虑讲解Template最常用的例子(为说明起见,未使用操作符重载等技术):
template<class Type>
Type Min(Type arg1,Type arg2)
{
if(arg1.comp(arg2))
return arg1;

return arg2;
}

在些例示中,只要Type实现了comp函数这个"约定",就可以使用该template。在Delphi中

,我们要如何做呢? 当然要先定义左兄所说TADT,要求重载comp函数。然后写Min函数:
function Min(arg1:TADT,arg2:TADT):TADT;
begin
if arg1.comp(arg2)
Result:=arg1;
else
Result:=arg2;
end;

在这种使用情况下,可以保证传入的参数都是TADT及其子类,不用RTTI也可以保证.comp调

用。但这并不是类型安全的:因为不能保证arg1和arg2是相同的子类型。如果用RTTI比较两者

类型,则面临着问题:如果类型不相同,返回什么值?还是抛出一个异常?
在C++的模板中,可以由编译器保证传入参数和传出参数的类型。而用RTTI的实现中,则是

绕过了编译器的检查,将保证类型安全的责任交到了程序员的手中。在左兄的实现中,栈所返

回的值也是TADT类型,要由程序员自己保证转换为正确的类型吧。就算有RTTI的帮助,这也并

非一件轻松的事。

说了这么多,其实大多是自己的胡乱猜想:)因为Delphi是我正在学习的语言。有错误的

地方,请大家指正吧。

[:)]
 
顶部