KeyFC欢迎致辞,点击播放
资源、介绍、历史、Q群等新人必读
KeyFC 社区总索引
如果你找到这个笔记本,请把它邮寄给我们的回忆
KeyFC 漂流瓶传递活动 Since 2011
 

[M] Prelude to K.O. (4)

[ 23477 查看 / 45 回复 ]

K.O.(KeyFC Open Translation Toolset) 是目前正在开发的“开放版本”汉化工具的代号。
其中的汉化部分工具试验性的采用了代号为Chobits Application Server(CAS)的架构。
这个结构的特点为,程序全部由基于消息传递的模块动态组合而成。

前面几回介绍了CAS架构的理念,内部结构,消息的接口、定义及传递。
至此,各位所了解的CAS还处于"空想模块化"阶段——理论上写出模块化的程序是可行的。
那么具体的实现上呢?这就是今天要谈的内容: CAS模块的结构、封装和程序的组装

--- CAS模块 ---

从理论模块化到实际模块化,CAS选择了一个最直接的方法:一对一"物理"映射。
也就是说,每一个CAS的"功能模块"都映射到一个实际的文件上。
文件的格式自然也就是"动态加载库",在Windows下叫DLL(Dynamic Loading Library),在Linux下叫SO(Shared Object)。

目前CAS仅有Windows的版本;但因为整个CAS除了少数地方使用同步对象外,并没有其他平台相关的代码,因此能够被很容易的移植到Linux下。
其中核心组件Lock-free队列我为了在8CPU环境下验证正确性已经实现在Linux下了(但是由于Linux pthreads的Condition同步对象缺少Timed Wait(带时限的等待)方法,暂时没有写出带锁同步队列作性能比较)。
有时间的话,我会尝试包裹Posix Thread中的同步对象(支持Timed Wait),这样移植CAS应该就没有什么大碍了。
(虽然Interface在CAS的使用是兼容COM规范的,但是其用途并不COMish,因此不影响Linux移植。)

书归正传,那么,也就是说模块的加载需要靠"动态加载库"文件(以下简称DLL)的接口来进行了。
(下面的部分将涉及一些抽象程序代码和具体API)

--- CAS模块封装 ---

因为世界上没有未卜先知,因此CAS模块的接口必须提供一个固定的方法集合才能够被正确的加载。
不过,因为每个人都有自己写加载方法的自由,因此这里讲的只能算"官方推荐",供使用者参考(当然,作为"官方使用者",K.O.系列的程序将遵循这里介绍的接口)


I: 首先要解决的问题是,怎样知道加载的DLL是合法的CAS模块?
因此,我规定每个CAS模块DLL都需要导出(Export)一个函数: ChobitsModuleSignature
这个函数返回一个64字节的记录(没有硬性规定意义,可以是代码的MD5,或者是数码签名等),目的是让加载方能够作选择性的加载。


II: 接下来,需要确定的是加载的模块使用的是兼容的接口和消息定义。
使用动态加载库的一个著名的问题就是"DLL地狱",由于同一个DLL的不同版本可能使用不同的接口,如果管理不善可能导致各种奇怪的问题。
为了解决好这个问题,我规定每个CAS模块DLL都需要导出(Export)另一个函数: ChobitsModuleInfo
这个函数返回多种信息,包括模块名称、GUID、消息队列长度、需要多少工作线程等等;
其中有一条信息(32位整数),称作"兼容总线版本",详细的定义如下:

* 高8位: 总线运行级别
  在前面的介绍中,我有意模糊了总线的具体功能,现在给出更清晰的定义。目前定义的总线有三个运行级:
  0. 静态总线: 仅提供了模块注册、查询和消息传送支持等基本功能;总线内部没有线程调度,因此不执行消息投递。好处是轻载(light weight),适用于消息较少的应用环境。(K.O.系列程序使用的就是这个总线)
  1. 投递总线: 顾名思义,就是增加了消息投递支持。适用于消息较密集的应用环境。(由于涉及到事件顺序,加上前段时间的总线代码重构(code re-factor),目前仍然在编写/调试中...)
  2. 管理总线: 在前一个级别的基础上,增加了一个管理线程,用于执行管理工作(总线统计信息收集、消息队列阻塞检测、失效队列回收;当然最重要的是接受队列注册、注销请求——有了这个功能,就可以使用消息来注册消息队列,这也就意味着,可以由一个模块来加载另一个模块)。这个总线适用于较大型的服务和应用程序,因为它可以自动支持一个动态的执行环境(比如动态的替换一个模块的库文件到新的版本,而不需要重新启动整个应用程序)。
  因为每一个运行级提供的支持都是前一个运行级的超集(Super-set),因此,自然的,兼容低级别总线的模块可以加载到高级别总线上,反过来则不行。

* 中间12位: 总线的主版本号
  前几回说到了,总线和模块之间唯一需要交互的两处就是: 模块消息接口——用于发送和接收消息;基本消息接口——用于处理消息的传递过程。
  同一个主版本号的总线将使用相同的模块消息接口和基本消息接口。换句话说,就是一旦模块消息接口和基本消息接口发生任何改变,主版本号将会提升。
  这也就意味着,兼容不同主版本号总线的模块一定不能被加载,不论谁高谁低。

* 后面12位: 总线的兼容版本号
  如果总线发生了一些细小的修订,但是没有更改模块消息接口或者基本消息接口,那么兼容版本号将会提升。
  因为接口并未改变,所以兼容旧版本总线的模块将可以使用在新的总线上;但是,因为兼容新总版本总线的模块将会期待总线一定程度上的变化,因此不能用于旧版本的总线。

最后举一下例子,以免有人看晕。以下版本号的格式为: [运行级别] [主版本号] [兼容版本号]
如果总线版本为:
2 2 2
模块兼容版本为:
2 2 2 可以加载
1 2 2 可以加载  (期待运行级别低可以)
3 2 2 不可以加载 (期待运行级别高不行)
2 2 1 可以加载  (兼容前一个修订版本可以)
2 2 3 不可以加载 (兼容后一个修订版本不行)
x y z 只要y不是2就不可以加载 (主版本不一致)


III: 接下来就是模块特定导出函数了。
在目前使用的规范中,只有两种不同的模块: 总线模块(是的总线也可以是一个单独的DLL :D) 和 应用模块
总线模块导出两个函数: CMB_Request (请求总线) 和 CMB_Drop (丢弃总线)
服务模块导出三个函数: CAM_Attach (接驳总线),CAM_Detach (脱离总线) 和 CAM_Finalize (结束应用)
(注意,因为目前CAS的应用仅仅在总线运行级别0上的,因此更高级别的总线以及对应服务模块*可能*会有更多的导出函数。但是这里已经列出的函数及接口将保持不变)

* 先谈总线模块 (Chibots Bus Module, CMB):

CMB_Request 需要一个总线ID参数,返回一个总线管理接口(Interface)。(是的,如果你愿意,同一个应用程序里面可以存在多个总线。总线之间是相互独立的,没有内置的交互能力,因此怎么使用嘛就是你的事了=v=)
如果指定ID的总线不存在则创建一个新的;如果不能创建(达到个数上限),则返回空指针。

CMB_Drop 需要一个总线ID参数。其功能是将符合ID的总线的内置记录清除,这样下一次请求同样ID时,将创建一个新的总线。
注意,因为总线是通过管理接口引用的,因此也是引用计数的。调用CMB_Drop和总线是否被释放没有任何关系。(当然,推荐合理的使用方法为即将释放总线前调用CMB_Drop,然后将管理接口置空,总线将被自动释放)

* 然后是服务模块 (Chobits Application Module, CAM):

模块加载程序在调用了前面提到的ChobitsModuleInfo,检查版本符合之后,应该根据同时返回的其他信息为正在加载的模块收集资源。(比如,使用返回的名称和GUID在总线上注册一个消息接口;同时生成要求数量的线程)
准备完毕后,调用服务模块的CAM_Attach,将资源递交给模块。
这时,模块应该做的事情有:
* 初始化内部结构
* 记录传递的消息接口
* 将传入的线程(一般来说,只有一个)分配到指定的任务(一般来说,是消息处理循环)


接下来,将会有很长一段时间DLL的接口不再有动静。因为所有的交互都通过消息接口在总线上进行...


当由于某种原因(一般来说,是程序结束),消息队列将被从总线取下,模块加载程序应当首先调用CAM_Detach。(如果顺序颠倒,服务模块在发送消息时将会受到一个异常: 队列已经从总线脱离)
收到这个调用的时候,服务模块应该做的事情是:
* 将分配的线程从任务脱离 (不需要终结,它们将会被线程池自动回收)
* 如果必要的话,做出适当清理,为下一个接驳总线的请求做好准备

当CAM_Detach返回后,模块加载程序就可以放心的从总线注销(一般来说是)消息队列了。

最后,当模块加载器需要卸载模块前,应该调用CAM_Finalize,这个时候模块要做的事情就是:
* 停止正在执行的操作(本来这个时候不应该有了,但是以防万一...)
* 释放申请的所有资源

当CAM_Finalize返回后,模块加载程序就可以放心的卸载模块DLL了。


在细节里面滚打了大半圈,让我们重新回到高处,俯瞰CAS架构。

--- CAS程序的组装 ---

因为运行级别3的CAS总线的操作和使用环境与前两个有一定不同,我们分开来看。

前两个级别的CAS总线需要由独立于总线的管理操作控制(加载/卸载应用模块),因此CAS应用程序的(建议)组装结构应该像下图:


(图1) CAS应用程序

* 处理应用程序图形界面(UI),同时负责初始化的线程最先被创建(主线程);
  这个线程创建一个管理线程,用于初始化和终止CAS架构,同时负责响应抽象用户请求。

* 初始化开始,总线模块首先载入,各个模块依次载入并在总线上注册,相应的模块线程被创建;
  初始化最后,第二个UI线程被创建(图中与主线程重合),用于将UI事件转换成抽象用户请求,以及将结果反映到UI上。
  (创建第二个UI线程的原因是,Windows有一套自己的窗体消息机制,(主要)用于和用户输入打交道,因此主线程有自己的消息循环需要处理。)

  什么?你问既然有了Window消息为什么还要CAS?晕...两套消息机制的设计目的以及用途非常不同,其他的(比如多线程同步效率)不说,一个很简单的例子就能说明问题:当你的鼠标划过一个窗口时,这个窗口的消息队列就会接受到成百条鼠标位置的信息,如果这个时候另外几个模块正在通过Window消息队列频繁的通讯的话,这个队列就会更长,导致所有的消息都得不到及时处理,这个时候的现象就是程序的窗口响应迟钝同时后台计算也变慢...可见将图形界面的消息和内部运算的消息混在一起的话,两者的处理都会受到影响。

* 回到刚才说的,所有线程都进入自己的消息循环,程序正式开始工作:
  用户的窗体操作被包装成为高级的应用请求消息,传递到管理线程;
  管理线程的程序将高级请求转换成为一系列的模块功能请求消息,然后分发到各个模块;
  (对于有顺序要求的消息,状态机也好,跨模块调用(IMC)也好,根据个人的风格和期望的效果实现,CAS是一个非常自由的架构! :D)
  根据各个模块反馈的结果,管理线程组装一个UI反馈消息,传递给UI线程,然后由其将消息解释成为图形界面的状态,反馈给用户。

* 最后,程序即将结束,管理线程首先通知各个模块队列取下,接下来依次注销消息队列,然后卸载各个功能模块,总线模块,最后返回;
  而主线程则一直等待线程池将所有线程回收,最后清理资源,终止程序。

注意图中有几条虚线,他们表示的意思是,UI线程和管理线程是作为功能模块挂接到总线上的,但是实际上他们并没有一个单独的DLL文件,因此称为"伪模块"(Pseudo-Module)。



级别2的CAS总线(管理总线)一般应用在大型的服务程序中。因为其本身具有一定的管理能力,因此CAS服务程序的(建议)组装结构应该像下图:


(图2) CAS服务程序

* 一般情况下,CAS消息总线将会和主程序形成一个模块;
  主线程创建后,首先创建消息总线(同前一种程序一样,可以创建多个,根据用途而定)
  然后,作为启动(Bootstrap)过程,主线程试图载入预先配置好的一个模块管理模块,注册并挂接;
  接下来,根据需要,主线程可以注册一个伪模块,以便和总线交互。(图中虚线省略了)

* 挂接后,具有管理功能的总线将向模块发出通知消息,模块管理模块捕获这个消息,并开始工作;
  模块管理模块可按照预设定或者某种特定顺序载入各个模块,每个模块独自捕获总线载入通知开始动作。
  注册/注销等消息队列管理操作都将以请求的方式发送给总线,由总线管理线程执行,并且消息队列取下的通知也由总线发出,各个模块应该独立相应。
  因此模块管理模块需要做的操作仅仅是载入,通知挂接,和卸载模块。

* 一般情况下,主线程没有其他事情可做,只需要等待总线的"结束程序"消息(CAS消息或者同步事件);
  然后,首先卸载模块管理模块(在此之前,总线已经通知其队列取下),接下来释放资源,结束程序。

如果需要用户界面,可以将其单独包装在一个模块中予以加载。

可以看得出来,服务程序的流程更加结构化;当然,这主要是因为总线实现了许多附加的功能。


------

现在,坚持跟进的各位应该对CAS架构有了一个初步的了解。如果各位还有什么不清楚的地方,欢迎回复反馈,我在合适的时候将集中解答。

最近正在对底层消息的实现作比较大的改变,主要是软件工程上的,比如统一总线内部对各种消息的处理等;
其中包括前面提到的,将总线对于IMC(跨模块调用)消息的支持通用化,使得基于CAS的开发者能够写出自定义的IMC消息。
当然,这些更改对于前面介绍的抽象概念和高层操作基本没有影响,请各位放心消化。 :P
最后编辑Prz 最后编辑于 2007-05-16 18:17:57
本主题由 管理员 深海蓝空 于 2007/5/17 13:35:24 执行 设置精华/取消 操作
分享 转发
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

感觉这目前这4章讲述的越来越乱了.其实只要几张图及少量说明就能了解总体框架了.
我一直认为"simple is best".这个系统现在看起来还是有些复杂,我们经常遇到越复杂的东西越容易出问题,模块开发者也会感到困惑和误用.一个模块可以简洁地只导出一个函数,返回接口指针即可,此时模块就可以做初始化,不必再调用attach.
当然我还没能全面了解,不清楚一些复杂功能的必要性.
COM就是为了跨编程语言而没有内在地使用智能指针,而多出了AddRef/Release的接口.当然C++的广泛应用使得大多数架构不必做到跨编程语言.
想必CAS也是用C++实现的吧.如果能提供一些sample代码,就能更清楚地了解运行的流程.
TOP

回复:[M] Prelude to K.O. (4)

处理几百条消息也用不了1秒,人感觉不到就行了,又不是设计导弹=。=

基本上现在不是消息处理不过来,是硬盘和内存之间交换数据才会卡的,窗口显示不了看看你的硬盘是不是狂转吧

看样子是不用MFC了,WIN32 SDK写个东西真麻烦……

『智代after PS2汉化移植完毕』www.keakon.cn
TOP

回复:[M] Prelude to K.O. (4)

...果然写得太多会导致人发晕...

1楼的意见都是针对封装的。
但是我一开始就提到,CAS的主要核心是程序的设计和实现方法,至于如何封装,是使用者个人的风格和程序的功能要求问题;各种不同的封装方法、形式都是完全可以支持的。
这一次的谈内容只是建议的方法,并且每一步的作用都有详细的解释,至于是否必要,就看个人对于最终用户体验的要求了。
比如,1楼提到可以直接扔一个接口了事。这种封装方法虽然也不影响正常情况的功能,但是小心我提到的"DLL地狱"——如果总线或者不同的模块编译时使用的接口不一致,轻则出现内存漏洞,或者程序AV(非法访问),更严重的可能导致模块的"非出错性"错误操作(表面看上去工作正常,实际上做的东西是完全错误的)。


2楼对于消息处理的理解也是有问题的。
其中描述的"窗口显示不了看看你的硬盘是不是狂转吧"仅仅是将一些表面现象机械的联系在一起。
我承认很多情况下“窗口显示不了”时硬盘确实在“狂转",但是,出现这个现象的根本原因还是窗口消息队列的阻塞:处理窗体消息的线程因为阻塞在了一些低速IO操作上,导致不能对窗口重绘事件及时响应。
而且,很多情况下这就是因为使用了我所描述的“不良”的消息处理机制——将程序的内部功能的消息与用户界面的消息混合在一个队列。
(是啊,平时写一次硬盘也就几十个毫秒,没什么大不了;但是关键是时刻就会让你等上几秒到几十秒,就等着窗口翻白吧......)

在CAS架构下,就算硬盘“狂转”,用户界面(窗口)依旧是响应灵活的,就是因为消息处理分离——处理窗口消息的线程只管绘制窗口,也就不会阻塞在低速IO上。
(当然,机器内存不足导致系统产生的大量页面交换的情况除外;不过这种情况下,任何用户程序都会响应迟缓,这就是另外一个问题了...)

(另, 1楼的"想必"是错误的... ^^ Windows版本的CAS是用Object Pascal实现的)


------

哦,对了,关于"越来越乱"的解释。
怎么说呢,还是老话:CAS代表的是一个方法、理念,引导使用者根据一定的原则设计程序,同时尽量给予使用者实现的自由。

举个具体的例子:Internet无疑是一个非常成功的架构,我们仔细看看Internet的结构,会发现一个"砂时计" (抽象概念的):

(应用协议)  HTTP FTP SMTP RTSP P2P ......
(传输协议)      TCP UDP ...
(路由协议)        IP
(物理协议)    ETHER ATM FDDI ...
(物理介质) 电话线缆 专用电缆 光纤 无线电 红外线 ......

不论底层还是顶层,看上去都很"乱"(褒义的讲叫"自由"),但是中间有IP坐镇,一切工作就有条不紊。


我设计CAS的时候,也有意的向这个成功的典范看齐:

服务模块 数据库 数学运算 图像处理 文法分析 加密解密 ......
消息分类    请求/回复 限时 功能调用 序列化 ...
通讯机制        消息总线
封装方法      应用程序 服务 ...
支持应用 WEB服务 媒体流传输 个人数据管理 游戏汉化 ......


这个宇宙中最"乱"(自由)的就是人的思想,因此上下两边到底有多少东西会以什么样的形式出现,我根本不企图去猜测,只需要集中精神做好中间的核心就行了。
最后编辑Prz 最后编辑于 2007-05-17 09:24:03
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

设计的简单灵活就可以把基本接口确定下来,这样每个模块返回的接口指针都不会有不兼容问题,可以用接口中的一个方法返回版本或是signature,不会有"dll hell"的问题.如果扩展接口,像COM那样提供QueryInterface就解决了.
我不知道Object Pascal的虚函数表是否和C++的虚函数表在二进制上兼容.
但我认为Object Pascal的使用范围非常狭窄,可移植能力也很有限.
要想发展,就必须考虑支持C++编写的模块.

另外,我没说CAS核心是很乱的,我上面说的只是这4次讲述是越来越乱的,
也许您的时间比较宝贵,或者急于奉献自己的宝贵思想.没有很好地整理.这个我可以理解.
最后编辑dwing 最后编辑于 2007-05-17 09:30:45
TOP

回复:[M] Prelude to K.O. (4)

其实选择语言是很重要的,没有虚函数/抽象函数的支持,COM和CAS都很难实现.
虽然Object Pascal支持抽象函数,但语言本身貌似还没真正标准化.
其使用者比C++少得多,编译器比C++少得多,优化效果也差些,写核心也用不上delphi的众多GUI控件.
所以我很怀疑用Object Pascal实现的原因.难道只是因为习惯问题?
TOP

回复:[M] Prelude to K.O. (4)

QueryInterface是提供了的,但是我在开发中曾经遇到过(可能是)因为通讯模块间Interface版本不一致 (编译两个模块中间修改了Interface的定义) 导致的一些问题,于是我设计了版本校验功能。
我不清楚如果两个使用同一名称、GUID的Interface如果定义不一致到底会产生什么样的问题;根据楼上的描述,似乎不会出现问题,但是我持保留态度。

另外,我曾经提到过CAS的Interface是符合COM特性的,楼上也提到过,使用接口就是为了解决使用不同语言/编译器的程序的通讯问题,因此Pascal和C++的代码的二进制兼容性不需要考虑。

每一种语言都有自己的使用人群,每一个人都是处于自己的一些个人喜好选择语言,因此我对楼上对Pascal的认识表示不屑:

虽然我不喜欢Java,但是我不得不承认,整个工业界正在以一种不可阻挡的趋势转向Java,那么楼上想要发展,我建议趁早抛弃C++转向Java..... (或者被Microsoft的这个#、那个.NET逐渐的诱导到一个他们专有的"Java"世界里去....)


------

我上面说的只是这4次讲述是越来越乱的


我明白你的意思,上一个回复也是针对这个意思回答的:
我认为你觉得乱是因为我介绍的内容的分类越来越多;而我的回答是,一开始讲的是核心,后来逐渐到外层,分类变多是正常的,而且我觉得是开放架构应该体现的优势。

当然也有可能是因为我试图在短短几回里面就把一个反复构思、试验、实现了数年的东西完整的摆出来的结果...=v= 没办法,我这是业余工作,不能占用太多时间......

另外...我不知道楼上从什么地方得知Pascal的语言没有标准化...
(我也没有看到什么地方说过Pascal的语言标准化了就是了... =v= 管它的呢,语言就是语言,标准化与否不是太重要,关键是美就行了)

还有,似乎楼上对于虚函数和抽象函数的担心有点多余:
Pascal是第一个真正支持OOP的高级语言,因此这方面的支持不仅不用担心,而且我认为应该比(标准)C++要早一些... (当然,仅仅是猜想,手里没有历史资料,如果错了也不要打我 :D)
最后编辑Prz 最后编辑于 2007-05-17 10:06:00
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

我还是认为: 如果设计足够简单并保留一定灵活性和扩展性,就可以把interface确定下来.
现在看起来比较好的pascal编译器只有borland在做,而C++,至少MS,GNU,Intel的编译器都非常完善.
我上面对Pascal的一部分认识应该成为共识了,现在仍用pascal/delphi的看起来只有习惯问题和RAD这两个原因了.如果还有哪些优势,请不吝赐教.
java的运行效率是致命弱点,跨平台能力也不是广告说的那样(我体验过).
.NET在这两方面比java更差,可能好处只是MS的支持和比较好的开发环境.

忽然想起Object Pascal好像不支持C++的模板特性,这样智能指针恐怕是不能实现了,这东西可以和COM很好地搭配,一般情况下不用手动管理对象引用计数,也很大程度上降低了内存无意泄露的问题.
最后编辑dwing 最后编辑于 2007-05-17 10:26:15
TOP

回复:[M] Prelude to K.O. (4)

相对于没有跨平台能力来说,Java就是强。还有其代码可维护性要比C++高得多,这就是Java在业界吃香的原因。

---
GNU编译器也有Pascal的版本,而且是Actively Maintained,好不好用没试过就是了。
Free Pascal也是一个很好的编译器,并且与Delphi语法高度兼容。

---
C/C++我也经常用,只不过只在Linux下(因为当初VS给我的印象极差,虽然听说自从从Borland挖人以后有很大改善,但是我都懒得去试 =v=)

C++里面有很多问题让我头痛,比如:

Pascal可以用下面的方法定义一个指向任意一个类符合接口的成员函数:
TFunc = Function (A: DWORD): DWORD of object;

C++就缺少这种形式的定义,只能用以下两种很“丑”的方法实现:
1. 直接将成员函数Cast成为指针然后赋值;
2. 使用Template,定义一个指向Template类的成员函数的指针;

第一种“丑”是因为需要强行Cast,而且不Type Safe,也就是说如果Cast回接口不一致的函数,程序一样高兴的去执行,然后自我崩坏...
第二种“丑”是因为每定义一个对象,都需要在后面加一行,套用Template产生一个只能指向这个对象的成员函数的类型,一点都不简洁,可用性还差...

如果楼上有什么更美观的解决办法,请不吝赐教。


写程序是一种艺术,因此每个人对于作品的审美观都不同,我喜欢Object Pascal是因为它有些地方(比如上面)很"美",是C++达不到的。
当然我也并不是说C++就不美;事实上我从来就没说过我讨厌C++,有些地方它比Pascal要美得多,比如写循环的语法上就比Pascal精练。
Java也是很"美"的,其纯OOP的语法虽然其实际使用中有的地方会造成语言冗余,这些地方是"丑"的,但是总的来说Java的程序结构就像一串漂亮的玻璃项链。

所以我的观点是,每个存在的语言都有自己的优点和存在的意义,因此仅仅因为自己不使用就否定其存在的价值是不正确的。

---
另外,说老实话,我不认为在目前主流OOP语法架构下,Template有什么不可替代的作用。
Template能够做到的事情,通过合理的设计类、处理继承关系,或者重载操作符,都可以做到。
最后编辑Prz 最后编辑于 2007-05-17 11:10:03
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

"Pascal可以用下面的方法定义一个指向任意一个类符合接口的成员函数:
TFunc = Function (A: DWORD): DWORD of object;"

真正深入学习编程语言就会理解每句代码是如何实现的.
C++几乎所有的代码我都能了解具体的实现方法.
但这句代码初看起来就有些问题.
C++中,用变量表示函数仅能使用函数指针.
这里的TFunc应该是指针吧.如果不是就会觉得很不可理解TFunc到底是什么结构.
如果是指针,这个值就是不固定的,因为许多类都可能符合这个接口,
而这些接口的地址都是不同的.
C++的函数指针只能指向全局或静态函数,指向某个非虚函数也有方法,
但指向虚函数的指针如何实现是不可想象的.
其实我们为什么只要这个成员函数呢,
C++中没遇到这个问题的原因是我们通常使用基类指针,
不必只要其中特定的某个成员函数.

我也不能说OP语言不优美,但我个人认为从语言本身的角度总体来看,确实与C++有差距.
最大的一点就是C/C++的预编译语言,尽管有人说它已经过时了,
但不得不说它使C/C++的开发适应能力达到极致.
借助预编译指令,C++工程是能够在主流平台编译的,现在许多开源工程都是如此.
C++的复杂和自由,还使人们经常能写出叹为观止的代码,智能指针就是一个例子.
C++不是没有跨平台能力,它是在源代码级跨平台的,而不是java的运行时跨平台.
不过就是因为源代码级跨平台太强了,int的范围竟不是固定的.
导致写一些核心函数时不敢直接用int,而是自己定义统一类型.(看来不可能十全十美)

java语言确实也和C++一样优美,可惜只是C++的一个子集(大致可以这么说).
其中的int是固定大小了,字符完全支持Unicode了.
但有一个致命的缺失--竟然不支持无符号整型,
有些时候就因为这个原因,程序不得不写的很复杂,效率很低.
当然一般只在系统/算法编程上.
所以说java只适合高层应用,不像C++那样应用广泛.
.NET要不是MS大力推广,能力上与C++比起来几乎没有优势.

嗯...感觉有些跑题了...

---
其实我也对Template没什么好印象,多数情况下确实用不到.
但"智能指针"是个特例,好像我只在这个地方用到了Template.
话说回来,"重载操作符"好像只是C++的特性吧-_-||
最后编辑dwing 最后编辑于 2007-05-17 11:49:18
TOP