刘未鹏|C++的罗浮宫

Knowledge sharing is the best reuse

用户操作
[即时聊天] [发私信] [加为好友]
刘未鹏ID:pongba
959232次访问,排名30好友45人,关注者228
兴趣:人工智能、机器学习、知识发现,认知科学。
pongba的文章
原创 105 篇
翻译 8 篇
转载 0 篇
评论 1818 篇
刘未鹏的公告
除非特别声明,本站采用Creative Commons License许可。转载请保留作者、出处。非商业。

FeedSkyFeedBurner
或者用 鲜果 GR 抓虾 订阅。

CSDN Blog暂时不支持RSS全文输出,对此感到不便的朋友可以使用强大的greasemonkey脚本:GReader Preview Enhanced(链接),该脚本支持在GReader里面直接打开全文页面。

我经常出没于TopLanguage讨论组

我的豆瓣TwitterDelicious

《C++的罗浮宫》5年选集

——知识分享是最大的复用

下载地址:csdn资源频道|mediafire

讨论问题请到TopLanguage小组

TopLanguage


gtalk/msn(邮件请发送到gmail邮箱)

pongba@gmail.com
pp_liu@msn.com

搜索C++的罗浮宫上的内容(不要回车,点击Go)

twitters

books I've translated




这个Blog上都写了哪些东东

最近评论
shupan:非常感谢你的文章,在看你的文章时, 我发现有很多类似的事情发生在我的身上。
kewan001:我也时不时的思考,关于如何学习,记忆等这些问题,但不曾看过什么著作,你带我看到了一个新的世界,谢过
biermando:收藏!
chenxiaoshun:恐怕是我在CSDN所看过的最好的文章。
xuxiandi:佩服啊。。。敬仰中。
文章分类
收藏
相册
其它图片
文章中的图片
我的大头贴
C++
Andrei Alexandrescu
Andrew Lumsdaine
Bjarne Stroustrup
boost
C++ Standard Commitee
Doug Gregor
Hans J. Boehm
Jaakko Jarvi
Jeremy G. Siek
Matthew Wilson
newsgroups
boost.Developer
boost.User
comp.lang.c++.moderated
comp.std.c++
TopLanguage
Open Source
Ant
codeplex
Danga
Google AJAX Search API
Google Code Prettify
Google Web Toolkit
Hadoop
MS shared source initiative
notepad++
STLSoft
不认识的朋友们
fatalerror99
Glacier
realazy
SpiritEpic
TK
Yelz
丁丁虫
冰云
刘慈欣
卢昌海
吴欣安(atppp)
姬十三
林达华
浦宇平
程化
阮一峰
高远
鲍盛
机器学习/数据挖掘/信息检索/自然语言处理/认知科学/人工智能
AAAI
arXiv
Charles Kemp
Christopher Bishop
Christopher Manning
Cognitive Daily
Dan Jurafsky
David MacKay
ECML PKDD
Geoffrey Hinton
Herbert Simon
ICML
IJCAI
Jeff Hawkins
Jiawei Han
JMLR
Josh Tenenbaum
Larry Wasserman
Lucene
Marvin Minsky
MIT AI Lab
MIT Computational Cognitive Science Group
Mitchell Marcus
ML
NetLab
NIPS
Peter Norvig
Stanford AI Lab
Stanford NLP Lab
Stephen Boyd
Tom Mitchell
Trends in Cognitive Science
Vladimir Vapnik
Weka
Zhihua Zhou
其它
Gigapedia
Scientific American
Scientific American Mind
科学松鼠会
科幻世界
认识的朋友们
alai
chenyufei
dd
DreamHead
duguguiyu|Venus神庙
Googol
Joyfire
littlestone
lxwde
Matrix67
realfun
RiceBall@cnBlogs
RiceBalll
roofalison
soloist
Tinyfool
windstorm
YongSun
书剑
云风
余晟|乱象&乱想
冯大辉(Fenng)
刘新宇
刘江@图灵
史晓明
周星星
周筠@博文视点
孟岩
张志强|阅微堂
张振
徐宥|4G Spaces&Web 2.3
方舟@博文视点
曾登高
李笑来|Pure Pleasure
杨文博
熊节
王信文|地球没有好朋友
王康生
荣耀
莫华枫
蒋涛
袁泳(g9)|负暄琐话
许式伟
谢东升
谷文栋|Beyond Search
陈冀康@华章
陈怀兴
鲍志云
存档
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 关于C++泛型编程的一些杂感收藏

新一篇: 《Imperfect C++》译序[已出版] | 旧一篇: 《Exceptional C++ Style》开放样章译稿

关于C++泛型编程的一些杂感

刘未鹏(pongba)

C++的罗浮宫(http://blog.csdn.net/pongba)

一些关于GP的思考或总结,没有太多的技术细节,主要是一些思想上的阐释。另外,文字比较乱,没有细细整理,凑合吧;-)

 

关于GP,可以说我是对它有很复杂的感情的,其实GP这种东西最好是建立在无类型语言上面,就C++0X目前对GP的支持的趋势来看,确实如此,auto/varadic templates这些特性的加入象征着C++ GP的形式正越来越转向一种更纯粹的泛性语法描述,表面上你几乎不会看到任何类型的痕迹,只有语法以及语法背后蕴涵的语义,然而在C++里面有一个最大的国情,即支持所有这些的是一个坚实的大地——强类型系统。所有的泛化所有的模板代码一旦实例化之后就落实到某一集特定的类型身上然后接受强类型系统的考验;-)有点像波函数的塌缩——本来是具有无数可能的,一旦有了一个观测者立即就塌缩成一个实体。在GP中,观测者就是使用者,或者说使用者给出的一集模板实参;-)

 

话说回来,虽说GP最好是建立在无类型语言,像LISP/Scheme这种语言上面,但它在C++里面却又确确实实的获得了极大的成功,这也正符合BSD&E里面的思想——现实总是需要折衷的,正应了中国的一句古话识时务者为俊杰。像LISP这样纯粹的语言到了现实应用当中往往是应用范围狭窄的同义词(不过用在教学和研究方面还是挺有意思的,虽然现在的主流FPL社区正在致力于将FPL应用到工业界去,但肯定还需要一段时间的;-))。BSC++从来都不是一门为漂亮而设计的语言,C++的语言特性都是从实际出发,实实在在的加进去的。另一个有趣的观察是,非主流的语言特性在主流语言当中往往能够得到很好的发挥,C++STLFPL风格初步运用到算法当中,算是获得了比较好的效果,至于一些更为纯粹的C++ FPLboost::lambdaboost::spirit::phoenixboost::bindboost::lambda::llfcpp等的运用则还处于摸索阶段。C++里面一个成功且必要的FPL风格运用是boost::mpl库里面的,由于C++ Metaprogramming并不支持side-effect(副作用),换句话说,在C++Metaprogramming当中,一切数据都是immutable的,所以像我们通常所见的for循环结构就不复存在了,转而成为递归结构。后者是FPL的招牌式结构;-)

 

C++GP的一个招人唇舌的地方儿就是它的语法,由于C++本质上是一门强类型语言,而且并没有内置的partial evaluationcurrying以及high order functional programming的支持,另外C++里面的函数也并非first class的对象。这些都使得我们在编写C++ FPL的库或通常的代码的时候感到处处掣肘,虽然利用一些神奇的技巧在C++里面是可以overcome这些缺陷的,但是语法,还是语法,有时候语法有点让人不可接受,当然,像我这样的热爱者会鼓吹说其实它的语法也不是那么差…;-)”。但毕竟跟LISPhaskell这样的原生FPL比起来,C++ FPL的语法还是显得有点生硬了,纯粹的FPL能够关注于表达代码的逻辑,理想情况下你看不到类型这回事。所以有人说haskell的表达就像数学一样简洁优美来着;-)但在C++当中你不得不受制于类型系统的束缚,有点像枷锁上的舞蹈,呵呵;-)

 

不过C++GP当中有一点奇妙的是,虽然我们熟知的runtime programming当中你并没有内建的对partial evaluation的支持,但在Metaprogramming里面却优雅的存在着,例如一个元函数plus,你可以写plus<_1,100>,这就是一个partial evaluationDavid在他的《C++ Template Metaprogramming》里面把这个称为”partial function application(部分函数应用)。但是在runtime的场景下这是不可能的,譬如一个runtime函数plus,你可以写plus(_1,100)吗?显然不可以。不过等一下,这种说法不够精确,如果plus是一个“lambda aware”的functor的话,这还是可行的,实际上已经有了这方面的完善的工作,语法是plus[_1,100],怪异吧,呵呵。但话说到底这只不过是二类公民而已,需要自己做大量工作,C++内建的函数并没有这个能力,例如对于:

 

  int plus(int i,int j);

 

你根本不可能使用plus(_1,2)。当然你可以重载出一个lambda awareplus版本使这成为可行的,但每次都要做这种重复劳动太浪费了;-)作为比较,为什么Metaprogramming具有这种能力呢?主要是因为以C++类模板为依托的C++元函数具有一个良好的FPL特性,即lazy evaluation(惰性求值),这种能力是C++内建的函数所没有的,对于一个内建函数如plus来说,你写plus()就等于是在写它的返回值,也就是说,evaluation会立即进行。但对于一个元函数plus<>来说,你写plus<>,求值并不立即进行,而是要等到你为它加上::value的时候,即plus<>::value,这才算完成了求值过程。换句话说,我们通常见到的函数,其求值过程是跟传参过程绑在一起完成的,求值就是传参,传参就是求值。但元函数则不同,你可以先传它一组参数,却可以在任意时刻去取它的返回值;-)

 

这就致使了C++ MetaprogrammingFPL能力从本质上是完备的和内建的;-)尽管语法仍然还是有点“那什么”;)

 

上边废话扯了一堆,下面是写毕业论文的时候的一些东西,比较基本(如果你愿意,也可以称为本质^_^),因为怕老师看不懂(@_@),老鸟就不必往下看了哈;-)

 

 

从语言层面上来说,现代的编程语言为复用提供了三种主要的基本途径。结构化、面向对象(OO)以及泛型(GP)。

 

结构化程序设计

 

结构化程序设计当中,提供复用性的语言特性主要是函数,在软件工程当中,函数可以被当成黑箱,实现一个或一组相关的功能(functionality),而用户不用关心函数内部的具体实现,只要负责将参数送入,然后接受返回值就可以了,C库函数就是极好的例子。

 

但是结构化程序设计有它本质上的缺点,这个缺点主要体现在代码的阻止上面,进而影响了可维护性。结构化程序设计的一个主导思想就是著名的“程序=操作+数据”,这里操作其实就意味着函数。虽然该论断一言道破了软件开发或程序设计的本质,但真正落实到实际开发当中,在成本控制方面,开发者还需要更强大的手段。譬如,结构化程序设计的一个严重问题就是,与一组数据相关的一组操作不能很好的被封装到一块去,例如,在C语言里面,我们要表达一个动物以及该动物的行为,我们只能采用一个接口,外加一组函数来表示。这种松散的组织方式就造成了理解和维护上的困难。而且,由于没有类的机制,函数的名字只能通过加上其对应类型的名字作为前缀来避免名字冲突。这不但增加了出错的机会,从某种程度上也增加了系统的混乱。所以说结构化程序虽然提供了过程/函数级别的复用,但是这种复用能力在当今软件开发当中是远远不够的。而且由于数据跟操作之间松散的组织方式,所以结构化程序并不是很适合大型而复杂的应用开发。之所以以前的一些操作系统,如UNIX/LINUX系列全是以C来编写,个人觉得,主要跟一些历史遗留因素有关,另一个因素是当时C++尚未发展得像今天这般成熟。至于效率方面,C++标准委员会提交的一则技术报告[TR]很直观的表示出,C++中的类机制跟用C来实现类似的封装能力不但效率不打折扣,甚至有过之而无不及。另外,一些大型的效率相关的应用使用C++来实现也正实现了这一点。譬如.NET整个的基层架构全是C++编写。而且开发大型的3D游戏,C++几乎是唯一的选择。可见在效率方面,并非像许多人一贯以为的那样,

 

面向对象的程序设计(OO

 

OO则提供了一种更为高层的抽象手段和复用机会,一个被良好OO化的系统中的大部分构造(construct)都应该是对象,对象与对象之间原则上通过消息来沟通,但大多数现代语言基于效率的考虑仍然是通过对象成员方法的调用来模拟消息的发送,这虽然带来了一定的耦合程度,但提高了效率,是一种合理的折衷(tradeoff)。此外,一个良好地抽象化的OO系统中的接口应该是相对稳定的,所以耦合于接口的对象之间仍然能够保持绝大部分的独立性和自由度。OO复用的成功的例子非常之多,著名的如微软的COM/DCOMOMGCORBA。其主要思想在于从对象层次上来封装一集相关的操作(或数据),对象向外部提供一组接口,每个接口提供一组相关的功能,比起原始的函数封装来说,OO中的对象不单具有概念上的清晰性,同时其功能性方面的内聚性,相关性也为复用提供了更直观友好的表达方式。而像COMCORBA这种大型的OO框架则更能提供位置无关的代码复用,乃至于抽象到了面向服务(Service Oriented)的层次,为更为强大的复用提供了契机。

 

 

面向对象(OO)程序设计的主要特点

 

紧绑定

 

然而,传统的OO实现有一个很大的弱点,即它是紧绑定/有限(bounded)的。举个例子,橡树(Oak)和苹果树(AppleTree)都是树(Tree)的一种,现在有一个树的集合(Set),需要对该集合排序,排序准则是基于树的高度,很显然,一个树要想能够加入这个有序集的话,就必须继承自Tree类,这就是一种紧绑定,一旦Tree基类有了改动,所有依赖于它的树都必须重新编译或改动,当然,一个设计良好的抽象基类是不应该常常改动的,但无论如何本质上的绑定是肯定存在的。而且,这个对该集合排序的算法只能被应用到树身上,因为它也是依赖于Tree抽象基类的。从另一个角度来说,只有树才能够被该算法排序。很显然的,人也具有高度,如果我们想要对一个Person Set进行同样逻辑的排序,我们就得重写该算法,这就意味着重复劳动,不但要付出编程心力,还可能隐藏着错误。当然,一个聪明的设计可能会对这种情况进行进一步的抽象,提取出一个所谓Comparable接口,所有能够比较的东西都继承自该接口。但这同样是一条荆棘遍布的道路,不但依赖的问题仍然没有消除(仍然依赖于Comparable,乃至于Comparable里面的方法签名),而且还可能出现类型混乱,譬如一个人(Person)具有Comparable接口,而一头大象(Elephant)也具有Comparable接口,那么对这个排序算法来说,它们就是可Compare的,这在现实当中可能是没有任何意义的,很可能会导致运行期异常的抛出。这会带来运行期的高昂代价。最关键的还是,这种做法强制每个Comparable的类型都必须实现Comparable接口,才能够利用该排序算法。后面我们将会看到,泛型编程完全解决了这个问题。不过,OO的紧绑定也为它带来了一个强大的优势,即二进制可复用性。二进制可复用性是一种强大的能力,一个最简单的例子就是C的库函数,它们的实现全都是放在二进制库当中的,用户唯一可见到的就是函数的头文件当中的声明。本质上,只要规定用户遵从某个二进制约定,就可以实现二进制复用,而类的继承,即OO的实现机制,在大部分现代语言当中,本质上就属于一种二进制约定。派生类的虚函数表跟基类的虚函数表必须布局一致,这样一来从二进制层面,派生类就能够被当作基类来使用了。当然,并非一定要牺牲松散耦合性才能够获取二进制复用性,换句话说,并非一定得使用类继承才能获得二进制复用性。目前之所以需要这么做,是因为绝大部分的语言都是将类继承机制建立在虚函数表之上,即二进制层面之上的。

 

效率

 

但是,OO在效率方面却显示出了先天性的不足,前面已经详细解释过,这种先天性不足是由于OO乃是建立在类继承体系之上的一种思想(至少目前的主流OO实现莫不如是),而且在主流OO实现当中,出于效率上的考虑,对象之间的消息传递都是基于方法的调用,进一步增加了耦合程度。这就使得基于OO的泛性构件只能够被应用到有限的一集对象上。而且,由于OO的基于继承的本质,实现泛性构件必然要用到动态转换,造成对于某些应用(如嵌入式系统,软实时系统乃是硬实时系统)可能无法承受的负担。这就是有名的abstraction penalty,意即抽象需要付出的代价。从另一个方面来说,也是从更本质的方面来说,这是由于没有将编译期的类型信息足够的利用起来。譬如说,JAVA(在没有引入JG