作者自述CSE语言设计思想(八)----CSE-Super语言设计思路(中)

 

归一化的数据结构

Super语言提供如下七种基本数据类型:

    整数(SInt)、长整数(SWint)、浮点数(SFloat)、字串(SStr)、

    整数列表(STier)、缓冲区(SBuff)、字典(SDict)

还有一种变体变量(SObject)类型。变体变量与其它语言中的Variant相当,七种基本类型均能用变体变量来表示。七种基本类型加上变体变量,共八种,它们都是可托管的,其变量生存周期由系统自动管理。

除上述八种类型,还有两种由用户拓展的数据类型:数组与用户自定义类型(User-DefinedType),前者数组与C++的“AType[]”对应,后者与C++的struct/union/class相对应。七种基础类型、变体类型、数据类型、UDT用户自定义类型,它们均用相同的数据结构表示(熟悉CSE的朋友知道,这个统一的类型是TCseObj),我们称“归一化”,指的就是用同一个数据结构表达各种类型。

归一化直接支持类型强制转换,因为保存数据的结构归一了,我们可用类型调用方式统一支持“引用式”类型强制转换(前文已有介绍)。类型归一了,函数缺省参数与变长参数容易支持起来,另外,构造事件驱动机制时,各个参数格式是统一的,不必像QT注册槽函数那么麻烦(要注册Meta元类型才行)。

 

 

事件驱动

事件驱动是一种基于回调机制的驱动,与常规回调函数不同,事件驱动具有缓冲功能,普通回调函数立即生效,调用后返回函数结果值,事件则不同,待调用的函数及其参数先纳入缓冲区,在恰当时候才批量(或单个)调用。

Super语言的事件驱动用起来很简单,如下:

entity.bind("onRefresh",super(me,iNumas SInt = 0,*args):
  ## do something
end);

entity.trigger("onRefresh",iValue,sValue);

entity.unbind("onRefresh");

bind预设事件响应函数,trigger触发事件,触发事件时可传递若干参数,unbind是删除事件。预设的事件响应函数可以像常规函数一个定义,参数可以是任意类型,可以指定缺省值,还可以带变长参数(如上面*args)。

我们既可以针对一个实体(Entity)操作事件,bind、unbind或trigger,也可以针对一个选择器(即实体的集合)批量bind、unbind或trigger事件。

 

 

闭包Closure

原型导向的编程系统中,闭包特性几乎不可缺失。因为是原型导向,面向对象的特性并不完备,如果不借助闭包变量,程序中大量定义不得不以全局变量的形式出现,代码量稍微多一点,全局变量数量容易失控,影响编程体验,比如:

super createStone():
  AStone as S.e("D2,Canvas,Stone").Stone;
 
  AStone.onload = super(me):
    AStone.color = SColor.c(255,0,0);
    ## ...
  end;
end;

上面“AStone”是闭包变量,它既是createStone函数中的局部变量,也是onload回调函数中直接可使用的变量。

众所周知,闭包变量如果使用不当容易导致内存泄露,这里我解释一下导致内存泄露的主要原因。拿上面例子来说,如果没有闭包特性,AStone变量的生存周期由createStone函数管理,当createStone函数调用结束,AStone不再使用,它会自动释放。现在使用闭包,变量AStone在闭包函数(即onload回调函数)中也使用了,所以闭包函数也引用AStone(它的引用计次累加1),这使得变量AStone在createStone函数调用结束时不会自动释放,只有闭包函数也删除了,这个变量才自动释放。这从表面看好象没问题,但深究一下,必然产生内存泄露,因为onload回调函数是否存活依赖于AStone是否存活。即:AStone变量是否存在依赖于闭包函数是否存在,而闭包函数是否存在又返过头依赖AStone变量,递归依赖了。

递归依赖由应用逻辑导致,在编程语言层面无法克服。Super语言相对JavaScript做了改进,引入了选择器的cache机制,被cache实体的递归依赖能自动解除,可有效规避内存泄露发生。

 

 

传值还是传址

C++中的参数或返回值传址(传引用)还是传值比较复杂,很容易误用。比如某函数调用返回“AType&”类型,既可能是最近用new创建的,需要接收者使用后将它delete掉,也可能传递已有变量(以引用方式返回,便于接收者修改它的属性),这时,如果接收者将它delete掉就出问题了。

好了,引用类型返回容易误用,那我们就借助拷贝构造函数吧,比如定义一个字串类型MyString,然后调用obj.desc()返回MyString类型,如:

MyString ObjType::desc()
{ return this->name; }

问题又来了,拷贝构造函数该如何处理呢?引用原有数据,还是另拷贝一份?如果引用原数据,程序性能很高,但如下使用方式会有问题:

MyString s = obj.desc();
s = "abcd";

因为s是你新定义的局部变量,对它赋值肯定不能该影响obj中已有取值,现在obj的name值连带被修改了,这种现象也常称为“边际效应”。

所以,MyString的拷贝构造函数不能采取引用方式,但采用拷贝一份相同数据的方式,程序性能又偏低,当MyString作为基础数据类型到处使用时,字串数据就到处拷贝,拷贝是隐含的(并未显式调用copy等函数),许多时候程序性能不足,你却不知道问题出哪儿。

传值还是传址对C++编程来说是个大命题,如果你很少关注,只能说明你还是个菜鸟,一方面,内存拷贝的消耗远高于常规API调用,另一方面,返回值与参数传递都遇到这个问题(上面只举了返回值的例子),与函数调用相关,编码中随处涉及。

我们希望为Super语言找到一个解决方案,规则非常简单,不能像C++那么复杂(不得不时刻提防用错),另外性能还要高。

这种机制还不能是copy-on-write机制,copy-on-write对C++来说是个不错的方案,其原理大致这样:拷贝构造函数采用引用方式(不拷贝对象数据),但在针对该对象“写”操作时判断当前对象是否与他人共享,若共享了,就立即为自己拷贝一份,然后在拷贝后数据上读写。这个方案有两个缺陷,一是需要定制,因为“写操作”你要捕获,二是不够彻底,有时编程需要曝露数据块(比如定义MyString.addr()返回字串地址),直接操作数据块就绕开了“写操作”捕获。

Super语言的七种基本数据类型(SInt、SFloat、SStr等)具有立即数特点,需要专门设计传值与传址机制,其它数据类型(variant、数组、UDT)均按对象数据看待,赋值与拷贝构造均按引用方式处理,若需用拷贝方式传递则显式定义一个copy类方法。七种基本类型在被写的状态下(指拷贝构造函数及变量赋值),若以脚本方式运行时,按拷贝方式处理,因脚本状态无法追求性能,我们保存逻辑正确就可以了,但在翻译或即时编译时,系统自动分析它在当前上下文中是否全部按“只读”方式使用的,若是,系统自动译为引用方式传递数据,若不是,自动译为拷贝传递方式。

这种处理方式很简单,翻译器参与语言解析,简单而又达到copy-on-write的执行效果。我们可以乐观的看到,用Super脚本开发的程序,即时编译后的运行性能并不比用C++差,如果你不刻意追求性能的话,用Super脚本做开发,即时编译后应该比用C++开发跑得要快。

 

 

C++库封装

Super语言依托C/C++开发系统,它的许多系统库用C/C++开发,经过简单封装就纳入到CSE-Super系统中使用。因Super语言的类型系统是可托管的,原有不可托管的C++类对象要以指针方式进行托管化改造。比如QImage是Qt中的class类:

typedef TMngHolder<QImage> TMngImage;

class SUIFRAME_EXPORT _Image: public _TMngUdt
{
    MNG_SUPER_BEGIN(_Image,_TMngUdt)
        TMngImage img;
    MNG_SUPER_END(_Image,_TMngUdt)
};

经此简单封装,QImage就纳入CSE-Super体系了,在C++中可以使用:

_Image image;
if (image->img->isNull())
    image->imag->loadFile("example.bmp");

被封装的class接口如数可用,是不是很简单。

 

(未完,待续)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值