http://www.lijianzhong.com/?p=7
这两天抽空在审校邓际锋(soloist)先生翻译的Bjarne Stroustrup为Embedded software and systems. 2005写作的《 Abstraction and the C++ machine model》一文。结合自己一段时间的C++培训经验,对C++的抽象有了更多的思考,在此作一简单总结,与朋友们交流。
为了将问题谈清楚,首先来谈谈抽象(Abstraction)这个词,wikipedia对Abstraction有如下解释:
Abstraction is the process of reducing the information content of a concept, typically in order to retain only information which is relevant for a particular purpose.
简单来说,就是“去粗取精”,或者“去不相关,取相关”。
这“一去一取”的目的何在?wikipedia也给了很好的解释:
Complexity reduction
Abstraction typically results in complexity reduction leading to a simpler conceptualization of a domain in order to facilitate processing or understanding of many specific scenarios in a generic way
总体而言,前面摘自wikipedia的两段话非常扼要地说明了“抽象”在我们认识事物过程中所扮演的关键角色——推开来说,人对世界的认识,实际上就是一个不断“抽象”的过程。“抽象”的力量普遍存在于各种学科,各个领域中。当然,具体到各个学科领域还是有一些具体的差别。
好,下面来具体谈谈C++中的抽象,或者说编程语言的抽象。从最根本性的目的来言,计算机就是对人的一种抽象——当然Turing的这个美好愿望要靠程序员来慢慢实现。编程语言在这个过程中扮演的角色就是将 “计算机容易理解的东西”抽象为“人容易理解的东西”。结合目前主流的编程语言(C++, C#, Java, VB.NET 等),举些例子具体来谈其中的抽象,就是让程序员:
基本的编程抽象
* 忘掉数据(无论对象/指针/引用)在内存中的地址,将精力集中在数据所表达的类型实例概念上
面向过程编程的抽象
* 忘掉函数调用的压栈/出栈细节,将精力集中在函数之间的调用关系上
基于对象编程的抽象
* 忘掉对象中数据成员(字段)的内存布局,将精力集中在数据成员对对象状态的表达上
* 忘掉对象中函数成员(方法)的绑定机制以及this指针,将精力集中在函数成员对对象行为的表达上
面向对象编程的抽象
* 忘掉类继承下子类对象中数据成员的内存布局,将精力集中在继承所带来的子类化的概念上
*忘掉虚函数相关的虚表vTable结构,将精力集中在虚函数所带来的动态多态的概念上
泛型编程的抽象
* 忘掉模板的各种编译与绑定机制,将精力集中在用一组抽象的概念来表达一组类型的需求条件上
面向组件编程的抽象
* 忘掉组件平台背后的元数据等机制,将精力集中在组件化模块所表达的黑盒概念上
这些“忘掉…而将精力集中在…上”的“抽象”放到C#, Java, VB.NET 等其他语言中,很多程序员都可以轻易做到——换句话说,可以不关心“各种抽象背后所映射的底层机器模型”,只关心语言表达的“抽象”,而照样开发出合格甚至优秀的程序。这样,这些语言下的程序员基本上遵循下面的学习路径,就可以成为一个合格的程序员:
掌握语言语法构造–>掌握设计思想(即抽象)–>开发应用程序 或者 程序库
但是如果放到C++,一个程序员无论如何不能够做到“忘掉…而将精力集中在…上”的“抽象”,否则连写出哪怕是正确运行的程序都很难。一个合格的C++程序员必须遵循下面的学习路径:
掌握语言语法构造–>掌握各种抽象所映射的底层机器模型–>掌握设计思想(即抽象)–>开发应用程序 或者 程序库
这就是C++中“抽象”的问题!C++程序员无法摆脱“各种抽象所映射的底层机器模型”而将精力单独集中于“抽象”上——换句话说,C++的抽象性和它的底层性是C++的一体两面,不能够像其他语言一样轻易分开。
那么是什么导致了C++这种独特的“不够彻底的抽象”呢?这种“不够彻底的抽象”到底有什么优劣呢?
Bjarne Stroustrup 在《 Abstraction and the C++ machine model》一文中重复了他在设计C++时一贯的哲学:
* 在切实可行的最高抽象层次上编程 Work at the highest feasible level of abstraction
所谓“切实可行”就是不损及效率,灵活,管理……简单地说就是C++希望在获得“抽象”的同时,仍然尽可能地不损失任何效率。C++一路发展过来,确实达到了这个目标。这正是C++“不够彻底的抽象”之原因。
这种“不够彻底的抽象”当然为C++赢得了巨大的成功,使得C++成为系统级软件的首选语言,是任何其它一门语言都无法望其项背,参见这些重量级的软件http://public.research.att.com/~bs/applications.html。
但这也使得C++在程序员圈子里一直是公认的难学难用。孟岩在C++开源程序库评话(节选) 中谈到用C++写优秀的程序库非常难这一事实。可惜只谈了“难”的结论,没有谈“难”的原因。事实上,C++并不仅仅在写程序库时难,用C++写应用程序同样不会轻松–相对其他语言而谈,只是C++写程序库要同时考虑的“抽象性和底层性”思维力度更大罢了。所有的根源都在于C++这种“不够彻底的抽象”。
我不知道“C++的抽象性和底层性这种一体两面的紧密结合”会在多大程度上损伤C++程序员学习的积极性,并从而影响C++应用的popularity,以及影响软件项目的质量和进度。 但至少对于目前希望成为C++程序员的朋友来讲,必须认识到“需要同时掌握C++语言抽象性和底层性”这个事实,才能将C++彻底掌握好,这也是我在目前不管是给企业,还是个人学员讲授C++培训课程时经常强调的。
当然,C++社区也意识到了这个问题,C++0x 也确立了一项“同时为专家和新手提供支持”的原则,参见Bjarne Stroustrup在去年C++软件技术大会上的发言《C++0x概览》。但是从目前来看,这个原则贯彻的并不能令人满意。例如,我不太相信如果一个C++程序员不清楚理解指针,对象,模板,concept(C++0x中的新东西)等所映射的底层机器模型,就能够轻松写出Bjarne在《C++0x概览》一文中最后演示的那个draw_all()的例子——虽然Bjarne Stroustrup期望所有C++程序员都认为它“如此简单!”
也许我们本来就不应该对C++期望太多,既想让它有极致的效率来构造系统软件,又想让它有纯然的抽象来满足变化无常的一般性软件开发——世界上好像没有十全十美的事情,当然也没有十全十美的语言:)