|
板凳
楼主 |
发表于 2011-10-22 18:22:34
|
只看该作者
7.11 C++的元编程和泛型编程
相比C#和JAVA,RUBY这样的语言来说,实际上即使是C++也没有直接在语言级上提供太多的抽象机制,而其它的语言,比如JAVA在语言级就有代理,RUBY在语言级就有混入。相比之下C++中只有OO和模板,(故称C++是第三代语言,而前者是第四代语言),而且C++的OO是二进制导向的,一直为后人稍稍诟病,但C++的模板技术却大受吹捧,它的抽象能力不小,首先它正用能得到generic programming,偏用能得到meta programming,而且丝毫不比第四代语言的那些抽象弱。这迎来了C++的再一次发展高峰。
模板中不需要变量这些运行期的设施,只需要type和常量,, 它又是语法导向的(所以其复用的手段往往是提供源程序,而且其缺点是不能提供强大的运行期能力,比如Croba),而不是二进制导向的,, 因为运行期才有变量的概念,我们知道C++是个强类型的语言,因此无法做到运行期的duck typing,但模板使它具有编译期的duck typing功能,OO的那种产生类型的机制是不纯粹的,可侵入的(我们称这种语言为面向对象),而duck typing才是好的产生类型的机制(我们称Ruby,python动态语言实现的OO为基于对象),而C++对于类型控制的能力在于它的编译期(因为它是强类型的),,而不是运行期的RTTI,实际上C++的RTTI是很弱的,它用来支持OO是需要花费运行期资源的,而作为一门静态类型语言的C++在编译期就有了关于类型的一切资讯(比如它实现多态就把这称为静态绑定),而如果将这种决策推迟到运行期(来实现运行期的OO多态),那么就必须借助于语言的RTTI机制。
meta programming是对模板的一种偏用,即trick而不是C++的一种技术,它是被发现而不是被发明的只有范型,人们发现编译期也能实现一些运算,比如模板中也可使用if else和递归这样的语言机制,但实际上模板不是语言,无法直接识别if else和递归,实际上它只是傻傻地实例化(即使是这样也能图灵完备),但是从另一种眼光来看,因为模板机制能间接支持 if else和递归,,所以它终究是一种图灵完备的系统,即模板实际上可以看成是C++的一种子语言,另一种意义上它也是C++的embeded language)。(比如它用这些tricks艰难地实现了一套编译期的stl,详见c++ meta programming那本书,而我们在用的源于HP的stl是运行期的)
最新的C++0x标准或许可以改变这种局面。C++0x标准为C++提出了一系列改革,的目的就是为能使元编程成为模板的正用。
7.12 元编程和函数式编程
lisp语言常常被作为教学语言,因为它源于严格的图灵理论,lamaba高阶函数理论,它的变体shemhe语言采用list,即广义表作为数据结构,这种数据结构可演化成任何一种数据结构,采用函数作为语言内first class来作为主要的开发范式,,,因为这种开发方式是图灵完备的,实际上它的计算能力是等价于任何一门语言的。理论上可产生一切逻辑。
C++中的函数并非first class,但它的元编程实现了一种metafunction,,这使得function(实际上是变形了的类模板)可以使C++在编译期实现“函数式编程”,(在这种意义上C++的模板就成了图灵完备的子语言了,实际上C++所谓的元编程主要是在模拟函数式编程)当然这种动作是深受限制的故而需要作抽象迂回的,(比如它的metafunction不像stl的仿函数是利用运行期支持的设施实现的,BOOST MPL库中的每个function独成一个文件,都需要include才能用,这主要是因为模板实现的函数并非类型,编译期并不认识函数调用及函数原型不能以函数(参数1,参数2)的形式进行调用,事实上它只认识模板以及相关机制,实际上你还可以看到编译期C++的好多奇特的语法都是源于编译期不完善的设施在看相关书籍的时候一定要处处提醒自己把握这个要点(除非改良C++标准以使编译器厂商支持编译期待性比如最新的C++09的提出),
我们下面再举一些编译期的语言设施及导致的迂回方式(某某说过,抽象是解决问题的一切方法)。
1.编译期只识别递归(比如在递归中一个特化可以指定递归结束条件,要知道递归首先是一种思想,,它成立的本质有二,1,自身用自身来定义2,结束条件,,,如果说template<类型>是递归的第一层意义,那么template<0>这样的特化体就是递归的第二层意义使得递归得于成立),
2,enum(变量就不能用,只能用类型和常数这样的编译期就确定下来的值比如typedef),struct(用struct而不用class是因为struct默认它的成员是public的)这样的语言设施,而且只有int,bool作为模板参数可用。
2,模板不能以模板为参数,,除非“类模板模板参数”,这样就不能直接产生foo(class foo1,class foo2),这样的结构,,,要绕一些弯子,比如tag dispatch function.
3,循环展开通常采用了复杂的迂回技术,
4,让模板的成员带value,模拟函数式编程的返回值。
5,因为元编程实际上是对产生关于类型逻辑的一种编译期策略(要知道多态化是设计的一个重要方面,编译期模板使C++变成动态语言或无类型语言,而这使C++成为跟Ruby,python一样的高抽象设计语言),它将要做的事情要完成的逻辑提早到编译期完成而不用到运行期的资源(因为C++模板赋于它这个能力可以这样做),所以通常可以用它来进行设计,模拟设计模式等等。因为模板是语法导向的,所以它的源程序是需要按源程序文件的发行的,进行语法级的复用(模板程序只需要编译前端就逻辑成立),而面向运行期的程序是二进制导向和复用的(事实上除了C++,没有那门语言有这样的能力吧),,这就是所谓的二进制复用,因为变量,class的内存映射,,全部是运行期的编译后端的事情。。
6,用list数据结构实现typelist.
写在后面的话:其实C++的元编程属于十分高级的抽象能力,我们不应该把精力放在这样的语言机制上面,基础薄弱的读者和程序员只需要把精力放在学好C++的运行期OO和数据结构就行了。因为大部分公司开发中甚至都不会用到这些技术。
7.13 C++的模板编译技术本质
编译器的工作在于将高级码转化为平台(这里的平台之意主要指CPU)目标码,但是现代的编译器和IDE还加了一些高级单元,如汇编器,装载器,重定位器,链接器,调试器,这些的功能来直接生成可运行文件.
一个编译器,如WINDOWS上的GNU,VC,BC等等,,对应于一个特定平台,编译器实现,链接器实现都是平台相关的(这里的平台主要指OS)往往有一个RUNTIME 库,,这个库是什么呢,,一个OS一般由C编成,这些C的库一般向程序(向编译器编译出来的,往往是用户空间的程序)提供CPU进程,,线程,SOCET等资源,运行库就是提供这些访问功能的地方,这就是编译器跟平台发生联系的地方,一般一个编译器都有它对应于某个平台的运行时库..
那么什么是编译期呢,什么是运行期呢?什么是编译期静态,什么是运行期动态?什么是静态语法错误,什么是动态逻辑错误呢?RTTI与RUNTIME有什么联系,为什么有new这样的动态分配内存.
编译期静态和运行期动态主要是指类型来说的,
那么泛型编程跟模板又有什么关系,一门语言的语言机制必定可在它的编译器实现找到答案.那么模板技术在编译器中是如何实现以支持泛型编程的呢
我们知道,一等公民在一门程序语言中是重要的,C++不支持数组的直接拷贝和赋值,不支持对象的直接赋值,不支持范型类型,这就是因为对象和数组是C++用其它方法抽象得到的语言机制,比如按位拷贝这种机器很擅长做的事,其实范型编程是可以用很多方法达成的,C++唯独用了编译器的"参数化类型"作法来实现模板再由模板实现泛型编程,而RUBY等语言有它自己的范型编程做法,,基于对象编程跟面向对象不同,不要以为你会写CLASS就是面向对象,如果没有用到OO的继承和多态,你同样是在写基于对象的东西.。
7.14 OO的缺点
比起函数式语言来说,因为在一门OO语言内它的一等公民不是函数体而是OO对象,而且OO直接跟我们的设计思想中出现的元素挂钩,因此OO成为代码复用和软工时代最好的语言,(软工这个字眼就指出它是计算机离散倾向人的结合.OO的提出就是为了解决编程离散形式与人思想中出现的因素的对接,解决复用和开发中人人的交流等问题,所以离开OO是不可想象的),但是构建在OO之上的设计模式却导致了越来越深的复杂性,这是因为人们没有想到OO以后的思想世界和现实世界应用,其实比OO这种形式要复杂得多,比计算机内部的离散形式还要复杂得多,换言之,OO并非一切,相反,在一定程序上提供方便性的同时,在另外一些维度和应用上导致了另外的复杂度,因为思想永远是复杂的嘛(杀鸡用了杀牛的刀)
而函数语言或动态语言却可以绕过设计模式实现同等的功能,因为函数作为first class是一种最接近计算机处理的形式(而OO同时接近计算机和人的思想,或者更确切地说是接近人的思想,看OO以后的GP,DP就知道了),,反而可以用这种唯一的形式绕过很多DP要花很多劲而函数式语言轻易就能搞定的一些东西.
但是不要认为动态语言就能提代OO静态语言(这种说法真可笑,复杂性有它的好处,复杂性同时也是相对的而已,RUBY在一些领域导致的复杂性比起JAVA来说会更多,只是不同情境的问题而已),哲学指出,任何问题都要实事求是,我们需要在问题需要什么类型的语言才去考虑选择什么语言,,考虑一门语言优劣时,并非纯粹技术问题,有时是多维的问题,,比如语言的优劣还得用它适用不适用软工这个指标来考虑呢
还比如JAVA的动态机制(反射加代理),这种历史复杂性是为了实现动态语言的"由行为决定类型",,,这就是AOP,,JAVA居然站在它本身是个静态语言的基础上去做动态语言做的事,造成了很多历史复杂性,讨厌
而且,RUBY这些宽形式,自由的语言,正是因为太自由了,就像C语言一样,太自由,反而不能成为软工的首先,因为软工要解决复用和简单意味着统一的形式的需要,即编程语言的离散形式和人的关系(所幸RUBY本身也提供了OO)..
所以,JAVA在WEB开发中,OO以后的那些WEB框架,是现实所导致的,并非JAVA本身导致的,,,改变J2EE框架就行了,并不要把JAVA改造或加个JRUBY就行的,,,相反,我们应更多地解决现实世界的思想模型,而非增大或扩展JAVA或JVM平台自身,感觉在JVM上集成JRUBY实在是个蠢的作法,凭空增大了抽象和运行时开销,就像把车拆了放到车库里明天又拼装起来再开一样..
其实以我来看,所有语言都是一种形式,要解决的问题才要弄明白,如果这种形式不能表达更深的问题,那么问题就出现在形式上,,,,OO最大的特点不是继承,不是多态,而是"OO接上了计算机离散和人的思想中的因素体,促成了软工时代的出现"
所以,不要去为了技术而探索技术,,,现实世界才是我们要最终解决的问题,而不是那些表达现实的形式语言..语言终究是形式.技术问题的复杂有时是历史原因导致的,,,,细节和理论,形式和应用,,是二个可以并行发展的维度...其中现实应用更重要...RUBY声称它能提供编程对于的简洁性,这就是一种很好的作法。
7.15 模板的继承
7.16 模板的偏特化
7.17 真正的策略
策略为什么跟设计模式有关?
设计跟编码之间无法精确定界,因为设计可以仅仅是提出一个用于实际编码所用的架构,也可以是考虑了所有编码细节的详尽设计,设计的最终目标可以是一张UML图,也可以仅仅是一张草图或一堆小卡片(Wildcard说法即来源于此,当然,并不一定要求设计要成档,但是将它具现化表达出来还是很好的行为),然而归跟结底,我们设计最终是想产生一堆有机的类文件(UML图也是,卡片也是),也即我们在进行设计时,我们的目标(Dest)是产生“编程领域对于现实事物或问题抽象的OO解(OO解只是解空间的其中一种而已,单纯使用OO的三重机制这只是初级和简单的范型,但已经是非常强大的范型,它产生的类文件已经可以实现出一个很大的架构,然而,结合了模板和策略的范型就更加是功能强大的范型了,可以更为灵活地产生出一个架构)”,这往往就是一系列的Class文件,而设计的源(Src)则是“经过了编程抽象的现实事物或问题”,设计模式就是用来描述这个过程的,然而它又跟算法不同,算法体现的是大领域内对于某个事物的解决方法(这就是为什么算法也可以用计算机来表达的原因所在)
我们知道设计是高于编码的,这就是说在我们进行设计时,进行的是一种纯思想的活动(非常重要的是这里的设计二字远非设计模式中的设计那么狭隘),并不预先想到要面向编码而设计,然而我们得出的某种设计方案(比如一种架构逻辑,,设计模式)并不是不可以在技术上用一种方式来实现,因为狭隘的设计模式之说的设计不过是根据设计方案产生一堆类文件而已,设计模式高于成码跟设计模式可以用一种技术来实现并不矛盾,关键是找不找得到一种技术实现手段的问题,你可以联系ISO网络参考模型来考虑,一种思想或协议的确可以用来实现。。就凭这点,我就十分佩服策略的提出者
策略是属于设计的而不是编码的(虽然策略也是写代码),,,这是本质,明白这点非常重要
策略就是这样一种技术,模板技术天然就用于参数化类(这是模板的本质抽象)并产生类文件,这在设计期是一个天然的技术实现,比如一个类文件可以产生一个职责或对应设计中的一个对象,而模板可以跟据对象本身的特征(比如对象的类型)去决定产生的表示这个对象的类逻辑,,而这正是策略要解决的问题
策略是多范型设计的较高境界,虽然OO也可很好地表达设计和思想,其它范型也可以,然而策略更接近设计,比如DP的Sington,Boost 的Enable If,,从这些字眼上看就直接跟思想和设计已经十分接近了。(不要忘记DP中一种是模板方法设计模式,,这更一步反映了模板与设计的深层渊源)
策略与OO是什么关系呢,(OO和OOP出现在前,,然后是DP,GP,Policy这些范型),因为模板就是一系列可运行的参数化类的逻辑,那么对这个过程也可以用OO(OO与策略并不是包含或被包含之类的关系)来实现,,因为模板也支持OO,比如STL的源码中,对容器,通用算法这些的策略大都没用到OO而是一堆面向过程的过程模板(函数模板就是子过程模板,类模板就是用到了一点OO的模板),,(如果策略本身也是用OO范型(通常说的OO就是类文件级的OO)来实现的,那么这可以称之为二阶OO,第一阶是1,模板产生类的过程中用到了OO,2类本身也是一种OO,)
因为策略就是模板技术描述的设计思想,设计思想可以是Gof,Boost,Loki,Core J2EE Spced DPS这样的大思路,也可以是一切反映设计的小思想,,因此策略不仅仅是我们现在已经见过的上述一些实现,,,它可以是千千万万的不同思想的模板实现形式(因为思想有千千万万嘛)
什么是编程,什么是抽象能力,编程能力,什么是学编程(即使这样看似最最简单的问题也其实不会简单,网上的确有太多这样的争论,那么这里将会给你一个有形式描述的概念,关于我在一个维度的认识,因为我们在上面提出了诸如设计,抽象,原语之类的用词,因此接下来的描性会变得容易界定,这就跟设计模式内部的那些功能分工的说法一样,,只有给出了能形式化的中间逻辑用词,那么才能不会在争辩时连最细小的中间共识都不能达成,那么也就不可能得出一个最后的结论),比如我在第五部分会写到一个世界逻辑库,因为要在这个逻辑世界里产生很多Actor,,而Actor本身又有Type,,Ablility之分,那么我可以用模板表示,,(ectc),如果你能像我这样想,那么恭喜,你已经在非常有成效地学编程了,,,而你在看我写的代码,,那么再一次恭喜你,你已经在实践了
所以什么是编程,,编程就是用各种范型来实现设计,,,
学编程就是在大范型之下学大量细节的小范型(所以说JAVA并不是一种很好的拿来作为教学语言的语言。因为它在OO方面和OO后面的事做的很好,然而在OO前面的那些范型全部被忽略了)
抽象能力就是对外来架构的理解能力,和掌握大幅中间逻辑,,以构造新逻辑的能力(计算机与人脑的区别就在这里,人脑可以主观能动地发明抽象,,而计算机只是我们用来反映抽象的工具,它本身并没有主动意识和主动构造抽象的能力)。
7.18 为设计产生代码
什么是模板会产生代码呢,这个说法其实无比简单,只是当我们摆出诸如“为设计而设计代码”,“编译期而非运行期多型””C++的元编程技术”这样的别称时,它就变得远离我们的理解变得面目生疏了,
我们知道,在编译型语言中,比如c和C++中,写代码就是为运行做设计,程序设计期design time的就法就因此而来,写代码的本质就是在C的数据封装抽象基础上,C++的基于对象抽象上,C++的OO抽象上这些语言机制逻辑上+操作系统封装了的CPU逻辑上,写最终面向CPU的二进制代码,这就是我们说的,系统编程语言的本质在于基于系统逻辑(并且语言本身也是基于系统逻辑的,我们把计算机系统离散基础,语言实现离散基础,CPU汇编语言所涉及到的那些机器离散基础都称为系统逻辑),写面向系统逻辑的代码。(与它对应的脚本语言更多地是为了直接为应用服务因为它极力让开发者绕过系统逻辑)。程序运行的本质是什么呢,,就是向操作系统和CPU请求资源,CPU执行二进制,(任何我们写出的源程序,在经编译优化后形成目标平台二进制码,我们向PC编程以构造应用的过程只是系统逻辑到应用逻辑的的一个维度变换),这是指比如C++这的以本地平台为运行环境的系统编程语言,而非指自带VM的,向VM编程的脚本语言而言的。
设计期就是写代码期,是用户动作,发生在编译期前,编译期不是用户动作,运行期又不是用户动作。。
模板是一种参数化的泛型,泛型这二个字决定了模板没有一个类型(在写模板函数,或模板类时的设计期,能难想象没有类型的东西能够堂里皇堂的参加运算吧^-^,只有编译器技术到家就可以),,在写出这个函数模板或类模板后经编译时它并不产生代码(机器动作时),只有在接下来实例化时(注意,这个实例化是指在设计期用户动作期,用户为这个泛型占位符指定一个具体类型并写出关于它的代码,这个实例化跟我们在OO程序设计中,由一个class定义一个对象类似,接下来,在编译期机器动作时才会在编译期实例化产生实际代码到中间目标文件中比如obj中(非飞行模式),
什么是实例化呢?注意这个字眼
在OO程序设计中,由一个class定义一个对象并不实际动态分配内存,因为这还是指为运行期作规划的设计期并没有实际进入到运行期并分配了一个内存,如果用了new关键字,还是没有分配内存,编译期永远不分配内存,只是为程序将来在运行期如何获得内存作分配上的控制而已,,
只不过一个实例化用了不同的情景而已,实例化并不一定指为运行期实际实例化(也就是说实例化并不一定就是一种“为运行而作设计”的设计,,它还可以是一种“为设计期而作设计”的设计期动作,)它其实就是一种编译期的宏,只不过它有类型的判断,因此远远不是简单的文字替换性质的宏,,而是一种编译技术,模板由于有这个特性,第一,因为它是泛型,所以为C++带来泛型编程机制,第二,模板给C++带来元编译技术。 |
|