转自http://blog.csdn.net/myan/article/details/5928531
ios
这是那篇C++0X的正文。太长,先写上半部分发了。程序员
Function/bind能够是一个很简单的话题,由于它其实不过就是一个泛型的函数指针。可是若是那么来谈,就没意思了,也犯不上写这篇东西。在我看来,这个事情要讲的话,就应该讲透,讲到回调(callback)、代理(delegate)、信号(signal)和消息传递(messaging)的层面,由于它确实是过重要了。这个话题不但与面向对象的核心思想密切相关,并且是面向对象两大流派之间交锋的中心。围绕这个问题的思考和争论,几乎把20年来全部主流的编程平台和编程语言都搅进来了。因此,若是详尽铺陈,这个话题直接能够写一本书。编程
写书我固然没那个水平,但这个题目确实一直想动一动。然而这个主题实在太大,我实在没有精力把它完整的写下来;这个主题也很深,特别是涉及到并发环境有关的话题,个人理解还很是肤浅,总以为我认识的不少高手都比我更有资格写这个话题。因此犹豫了好久,要不要如今写,该怎么写。最后我以为,确实不能把一篇博客文章写成一本20年面向对象技术史记,因此决定保留大的架构,可是对其中具体的技术细节点到为止。我不会去详细地列举代码,分析对象的内存布局,画示意图,可是会把最重要的结论和观点写下来,说得好听一点是提纲挈领,说的很差听就是语焉不详。但不管如何,我想这样一篇东西,一是谈谈我对这个事情的见解,二是“抛砖引玉”,引来高手的关注,引出更深入和完整的叙述。安全
下面开始。网络
0. 程序设计有一个范式(paradigm)问题。所谓范式,就是组织程序的基本思想,而这个基本思想,反映了程序设计者对程序的一个基本的哲学观,也就是说,他认为程序的本质是什么,他认为一个大的程序是由什么组成的。而这,又跟他对于现实世界的见解有关。显然,这样的见解不可能有不少种。编程做为一门行业,独立存在快60年了,可是所出现的范式不过三种——过程范式、函数范式、对象范式。其中函数范式与现实世界差距比较大,在这里不讨论。而过程范式和对象范式能够视为对程序本质的两种根本不一样的见解,并且可以分别在现实世界中找到相应的映射。架构
对象范式与过程范式相比,有三个突出的优点,第一,因为实现了逻辑上的分工,下降了大规模程序的开发难度。第二,灵活性更好——若干对象在一块儿,能够灵活组合,能够以不一样的方式协做,完成不一样的任务,也能够灵活的替换和升级。第三,对象范式更加适应图形化、网络化、消息驱动的现代计算环境。并发
因此,较之于过程范式,对象范式,或者说“面向对象”,确实是更具优点的编程范式。最近看到一些文章抨击面向对象,说面向对象是胡扯,我认为要具体分析。对面向对象的一部分批评,是冲着主流的“面向对象”语言去的,这确实是有道理的,我在下面也会谈到,并且会骂得更狠。而另外一个批评的声音,主要而来自STL之父Alex Stepanov,他说的固然有他的道理,不过要知道该牛人是前苏联莫斯科国立罗蒙诺索夫大学数学系博士,你只要翻翻前苏联的大学数学教材就知道了,可以在莫大拿到数学博士的,根本就是披着人皮的外星高等智慧。而咱们编写地球上的程序,可能仍是应该以地球人的观点为主。app
1. 重复一遍对象范式的两个基本观念:框架
请注意,这两个观念与后来咱们熟知的面向对象三要素“封装、继承、多态”根本不在一个层面上,却是与再后来的“组件、接口”神合。编程语言
2. 世界上第一个面向对象语言是Simula-67,第二个面向对象语言是Smalltalk-71。Smalltalk受到了Simula-67的启发,基本出发点相同,但也有重大的不一样。先说相同之处,Simula和Smalltalk都秉承上述对象范式的两个基本观念,为了方便对象的构造,也都引入了类、继承等概念。也就是说,类、继承这些机制是为了实现对象范式原则而构造出来的第二位的、工具性的机制,那么为何后来这些第二位的东西篡了主位,后面我会再来分析。而Simula和Smalltalk最重大的不一样,就是Simula用方法调用的方式向对象发送消息,而Smalltalk构造了更灵活和更纯粹的消息发送机制。
具体的说,向一个Simula对象中发送消息,就是调用这个对象的一个方法,或者称成员函数。那么你怎么知道可以在这个对象上调用这个成员函数呢?或者说,你怎么知道可以向这个对象发送某个消息呢?这就要求你必须确保这个对象具备合适的类型,也就是说,你得先知道哦这个对象是什么,才能向它发消息。而消息的实现方式被直接处理为成员函数调用,或虚函数调用。
而Smalltalk在这一点上作了一个历史性的跨越,它实现了一个与目标对象无关的消息发送机制,无论那个对象是谁,也无论它是否是能正确的处理一个消息,做为发送消息的对象来讲,能够毫无顾忌地抓住一个对象就发消息过去。接到消息的对象,要尝试理解这个消息,并最后调用本身的过程来处理消息。若是这个消息能被处理,那个对象天然会处理好,若是不能被处理,Smalltalk系统会向消息的发送者回传一个doesNotUnderstand消息,予以通知。对象不用关心消息是如何传递给另外一个对象的,传递过程被分离出来(而不是像Simula那样明确地被以成员函数调用的方式实现),能够是在内存中复制,也能够是进程间通信。到了Smalltalk-80时,消息传递甚至能够跨越网络。
为了方便后面的讨论,不妨把源自Simula的消息机制称为“静态消息机制”,把源自Smalltalk的消息机制称为“动态消息机制”。
Simula与Smalltalk之间对于消息机制的不一样选择,主要是由于二者于用途。前者是用于仿真程序开发,然后者用于图形界面环境构建,看上去各自合情合理。然而,就是这么一点简单的区别,却形成了巨大的历史后果。
3. 到了1980年代,C++出现了。Bjarne Stroustrup在博士期间深刻研究过Simula,很是欣赏其思想,因而就在C语言语法的基础之上,几乎把Simula的思想照搬过来,造成了最初的C++。C++问世以之初,主要用于解决规模稍大的传统类型的编程问题,迅速取得了巨大的成功,也证实了对象范式自己所具备的威力。
大约在同期,Brad Cox根据Smalltalk的思想设计了Objective-C,但是因为其语法怪异,没有流行起来。只有Steve Jobs这种具备禅宗美学鉴赏力的世外高人,把它奉为瑰宝,与1988年连锅把Objective-C的团队和产品一口气买了下来。
4. 就在同一时期,GUI成为热门。虽然GUI的本质是对象范型的,可是当时(1980年代中期)的面向对象语言,包括C++语言,还远不成熟,所以最初的GUI系统无一例外是使用C和汇编语言开发的。或者说,最初的GUI开发者硬是用抽象级别更低的语言构造了一个面向对象系统。熟悉Win32 SDK开发的人,应该知道我在说什么。
5. 当时不少人觉得,若是C++更成熟些,直接用C++来构造Windows系统会大大地容易。也有人以为,尽管Windows系统自己使用C写的,可是其面向对象的本质与C++更契合,因此在其基础上包装一个C++的GUI framework必定是垂手可得。但是一动手人们就发现,彻底不是那么回事。用C++开发Windows框架可贵要死。为何呢?主要就是Windows系统中的消息机制其实是动态的,与C++的静态消息机制根本配合不到一块儿去。在Windows里,你能够向任何一个窗口发送消息,这个窗口本身会在本身的wndproc里来处理这个消息,若是它处理不了,就交给default window/dialog proc去处理。而在C++里,你要向一个窗口发消息,就得确保这个窗口能处理这个消息,或者说,具备合适的类型。这样一来的话,就会致使一个错综复杂的窗口类层次结构,没法实现。而若是你要让全部的窗口类都能处理全部可能的消息,且不论这样在逻辑上就行不通(用户定义的消息怎么处理?),单在实现上就不可接受——为一个小小的不一样就得创造一个新的窗口类,每个小小的窗口类都要背上一个多达数百项的v-table,而其中可能99%的项都是浪费,不要说在当时,就是在今天,内存数量很是丰富的时候,若是每个GUI程序都这么搞,用户也吃不消。
6. 实际上C++的静态消息机制还引发了更深严重的问题——扭曲了人们对面向对象的理解。既然必需要先知道对象的类型,才能向对象发消息,那么“类”这个概念就特别重要了,而对象只不过是类这个模子里造出来的东西,反而不重要。渐渐的,“面向对象编程”变成了“面向类编程”,“面向类编程”变成了“构造类继承树”。放在眼前的鲜活的对象活动不重要了,反而是其背后的静态类型系统成为关键。“封装、继承”这些第二等的特性,喧宾夺主,俨然成了面向对象的要素。每一个程序员彷佛都要先成为领域专家,而后成为领域分类学专家,而后构造一个完整的继承树,而后才能new出对象,让程序跑起来。正是由于这个过程太漫长,太困难,再加上C++自己的复杂度就很大,因此C++出现这么多年,真正堪称经典的面向对象类库和框架,几乎屈指可数。不少流行的库,好比MFC、iostream,都暴露出很多问题。通常程序员总以为是本身的水平不够,因而下更大功夫去练剑。却不知根本上是方向错了,脱离了对象范式的本质,企图用静态分类法来对现实世界建模,去刻画变化万千的动态世界。这么难的事,你水平再高也很难作好。
能够从一个具体的例子来理解这个道理,好比在一个GUI系统里,一个 Push Button 的设计问题。事实上在一个实际的程序里,一个 push button 到底“是否是”一个 button,进而是否是一个 window/widget,并不重要,本质上我根本不关心它是什么,它从属于哪个类,在继承树里处于什么位置,只要那里有这么一个东西,我能够点它,点完了能够发生相应的效果,就能够了。但是Simula –> C++ 所鼓励的面向对象设计风格,非要上来就想清楚,a Push Button is-a Button, a Button is-a Command-Target Control, a Command-Target Control is-a Control, a Control is-a Window. 把这一圈都想透彻以后,才能 new 一个 Push Button,而后才能让它工做。这就形而上学了,这就脱离实际了。因此很难作好。你看到 MFC 的类继承树,以为设计者太牛了,能把这些层次概念都想清楚,本身的水平还不够,还得修炼。实际上呢,这个设计是通过数不清的失败和钱磨出来、砸出来的,MFC的前身 Afx 不是就失败了吗?1995年还有一个叫作 Taligent 的大项目,召集了包括 Eric Gamma 在内的一大堆牛人,要用C++作一个一统天下的application framework,最后也以惨败了结,连公司都倒闭了,CEO车祸身亡,牛人们悉数遣散。附带说一下,这个Taligent项目是为了跟NextSTEP和Microsoft Cairo竞争,前者用Objective-C编写,后来发展为Cocoa,后者用传统的Win32 + COM做为基础架构,后来发展为Windows NT。而Objective-C和COM,偏偏就在动态消息分派方面,与C++迥然不一样。后面还会谈到。
客观地说,“面向类的设计”并非没有意义。来源于实践又高于实践的抽象和概念,每每能更有力地把握住现实世界的本质,好比MVC架构,就是这样的有力的抽象。可是这种抽象,应该是来源于长期最佳实践的总结和提升,而不是面对问题时主要的解决思路。过于强调这种抽象,无异于假定程序员各个都是哲学家,具备对现实世界准确而深入的抽象能力,固然是不符合实际状况的。结果呢,刚学习面向对象没几天的程序员,对眼前鲜活的对象世界视而不见,一个个都煞有介事地去搞哲学冥想,企图越过现实世界,去抽象出其背后本质,固然败得很惨。
其实C++问世以后不久,这个问题就暴露出来了。第一个C++编译器 Cfront 1.0 是单继承,而到了 Cfront 2.0,加入了多继承。为何?就是由于使用中人们发现逻辑上彷佛完美的静态单继承关系,碰到复杂灵活的现实世界,就破绽百出——蝙蝠是鸟也是兽,水上飞机能飞也能游,它们该如何归类呢?原本这应该促使你们反思继承这个机制自己,可是那个时候全世界陷入继承狂热,因而就开始给继承打补丁,加入多继承,进而加入虚继承,。到了虚继承,明眼人一看便知,这只是一个语法补丁,是为了逃避职责而制造的一块无用的遮羞布,它已经彻底已经脱离实践了——有谁在事前可以判断是否应该对基类进行虚继承呢?
到了1990年代中期,问题已经十分明显。UML中有一个对象活动图,其描述的就是运行时对象之间相互传递消息的模型。1994年Robert C. Martin在《Object-Oriented C++ Design Using Booch Method》中,曾建议面向对象设计从对象活动图入手,而不是从类图入手。而1995年出版的经典做品《Design Patterns》中,建议优先考虑组合而不是继承,这也是尽人皆知的事情。这些迹象代表,在那个时候,面向对象社区里的思想领袖们,已经意识到“面向类的设计”并很差用。只惋惜他们的革命精神还不够。
7. 你可能要问,Java 和.NET也是用继承关系组织类库,并进行设计的啊,怎么那么成功呢?这里有三点应该注意。第一,C++的难不只仅在于其静态结构体系,还有不少源于语言设计上的包袱,好比对C的兼容,好比没有垃圾收集机制,好比对效率的强调,等等。一旦把这些包袱丢掉,设计的难度确实能够大大降低。第二,Java和.NET的核心类库是在C++十几年成功和失败的经验教训基础之上,结合COM体系优势设计实现的,天然要好上一大块。事实上,在Java和.NET核心类库的设计中不少地方,体现的是基于接口的设计,和真正的基于对象的设计。有了这两个主角站台,“面向类的设计”不能喧宾夺主,也能发挥一些好的做用。第三,如后文指出,Java和.NET中分别对C++最大的问题——缺乏对象级别的delegate机制作出了本身的回应,这就大大弥补了原来的问题。
尽管如此,Java仍是沾染上了“面向类设计”的癌症,基础类库里就有不少架床叠屋的设计,而J2EE/Java EE当中,这种形而上学的设计也很广泛,因此也引起了好几回轻量化的运动。这方面我并非太懂,可能须要真正的Java高手出来现身说法。我对Java的见解之前就讲过——平台和语言核心很是好,但风气很差,崇尚华丽繁复的设计,装牛逼的人太多。
至于.NET,我听陈榕介绍过,在设计.NET的时候,微软内部对因而否容许继承爆发了很是激烈的争论。不少资深高人都强烈反对继承。至于最后引入继承,很大程度上是营销须要压倒了技术理性。尽管如此,因为有COM的基础,又实现了很是完全的delegate,因此 .NET 的设计水平仍是很高的。它的主要问题不在这,在于太急于求胜,更新速度太快,基础不牢。固然,根本问题仍是微软没有可以在Web和Mobile领域里占到多大的优点,也就使得.NET没有用武之地。
8. COM。COM的要义是,软件是由COM Components组成,components之间彼此经过接口相互通信。这是否让你回想起本文开篇所提出的对象范型的两个基本原则?有趣的是,在COM的术语里,“COM Component ” 与“object ”通假,这就使COM的心思昭然若揭了。Don Box在Essential COM里开篇就说,COM是更好的C++,事实上就是告诉你们,形而上学的“面向类设计”很差使,仍是回到对象吧。
用COM开发的时候,一个组件“是什么”不重要,它具备什么接口,也就是说,可以对它发什么消息,才是重要的。你能够用IUnknown::QueryInterface问组件能对哪一组消息做出反应。向组件分派消息也不必定要被绑定在方法调用上,若是实现了 IDispatch,还能够实现“自动化”调用,也就是COM术语里的 Automation,而经过 列集(mashal),能够跨进程、跨网络向另外一组件发送消息,经过 moniker,能够在分布式系统里定位和发现组件。若是你抱着“对象——消息”的观念去看COM的设计,就会意识到,整个COM体系就是用规范如何作对象,如何发消息的。或者更直白一点,COM就是用C/C++硬是模拟出一个Smalltalk。并且COM的概念世界里没有继承,就其纯洁性而言,比Smalltalk还Smalltalk。在对象泛型上,COM达到了一个高峰,领先于那个时代,甚至于比它的继任.NET还要纯洁。
COM的主要问题是它的学习难度和安全问题,并且,它过于追求纯洁性,彻底放弃了“面向类设计” 的机制,显得有点过。
9. 好像有点扯远了,其实仍是在说正事。上面说到因为C++的静态消息机制,致使了形而上学的“面向类的设计”,祸害无穷。但实际上,C++是有一个补救机会的,那就是实现对象级别的delegate机制。学过.NET的人,一听delegate这个词就知道是什么意思,但Java里没有对应机制。在C++的术语体系里,所谓对象级别delegate,就是一个对象回调机制。经过delegate,一个对象A能够把一个特定工做,好比处理用户的鼠标事件,委托给另外一个对象B的一个方法来完成。A没必要知道B的名字,也不用知道它的类型,甚至都不须要知道B的存在,只要求B对象具备一个签名正确的方法,就能够经过delegate把工做交给B的这个方法来执行。在C语言里,这个机制是经过函数指针实现的,因此很天然的,在C++里,咱们但愿经过指向成员函数的指针来解决相似问题。
然而就在这个问题上,C++让人扼腕痛惜。