简明C++进化史

曾经我想学习C++。所以我决定在知乎上搜索,以便能对这个语言有个大概的印象。但是在那里我得到了太多的抱怨。其中一个最明显的观点是:C++是一门复杂的语言。这阻碍了我这样一个初学者继续学习C++。知乎的极力劝退迫使我思考:是不是有其他更好的语言可以代替C++。

但是我发现当我使用UE想要开发游戏的时候,当我需要做一些密集型运算的时候,当我需要各种各样的数据结构写出精壮的代码的时候。我发现我还是需要C++。但就是因为对C++这样那样的偏见导致我远离C++。其实这些偏见是没有必要的,因为基于它们的基本事实是错的。那就是:C++不是一个复杂的语言。问题在于C++的声誉不太好,这导致人们不想学习C++。而另一个麻烦在于没有高质量的教学材料。要解决第一个问题,了解C++进化史就很有必要。

CPP在过去的40年是从C语言演变而来的。它从一开始就被当作是C语言的分支,只是多了一些额外的特性罢了。在当时有一种预编译器叫CFront。它将早期的C++代码转换成C语言代码,然后再交由C语言编译器进行编译。所以这个预编译器才被称为CFront(In front of C)。那么经过几年的进化和发展,这被证明限制了语言的发展。所以是时候制作一个真正的C++编译器了。

当时有个新编译器可以独立地编译C++。它的作者是Bjarne Stroustrup,同时他也是C++语言的发明者。在当时,其他公司还对继续提供基本的C语言支持感兴趣。并且制作了自己的C++编译器,它们大多数与Cfront或较新的编译器兼容。但这条路被证明是行不通的,因为这导致语言在不同的编译器之间有大范围的不兼容。

另外,将C++所有的决策和发展方向都交由一个人掌握是不合理的。这并不是制定跨公司国际标准的方法,因此需要一个标准的程序并由组织代劳。这个组织就是ISO(International standard organization),国际标准化组织。ISO专门制定各种国际标准。C++当然需要一个统一的标准,所以它也就成为了一个ISO标准,并由ISO进行管理。1998年,第一个官方发布的C++标准问世,大家都灰常开心。

但是大家开心了没多久,问题就来了。尽管C++98对C++进行了很好的定义。但它也包含了一些没有出现的特性。并且一些功能之间的交互方式非常的怪诞。比如,你虽然可以用std:String类型来存储文件名。但是你却不能把它作为参数来打开文件,因为没有这种实现。

另一个较晚添加的功能是模板功能。它是C++标准模板库(STL——Standard template library)的底层实现技术。而C++标准模板库是当今C++的重要组成部分。STL存在的意义有三点:

  1. 它是图灵完备的
  2. 许多复杂的构造可以在编译阶段计算推出。这增强了标准库撰写人员撰写更通用的代码的能力。因为它可以处理任意复杂的推断。
  3. 在当时,其他现存的语言里面是不存在的这种功能的。

我们提到了模板,因为最后一个麻烦和它相关。就是,虽然C++ 98很好,但是许多编译器并不适合实现模板功能。当时的主流编译器GNU的GCC 2.7和微软的VC++ 6.0都无法实现编译模板功能要求的两步名称查找(two-step name lookup)(“两步名称查找” 是一个非正式术语,指在编译模板时分两步进行解析(编译)。每一步有不同的作用)。解决这个问题唯一完满的方法是重写编译器。

GNU一开始尝试在现有代码的基础上做开发。但最终决定在2.95版本这个时间点左右重写。这同时也意味着在几年内,GCC不会有新的功能和版本发布。很多人就觉得不爽。于是一些公司还基于2.95这个版本,自行开发了2.95.2,2.95.3甚至是2.96。这三个版本都由于不稳定让人印象深刻。最后,完整的GCC 3.0终于横空出世。

3.0版本一开始并不是很成功。因为尽管它可以比2.95版本更好地编译模板代码。但是却没办法编译出能跑的Linux内核了(注:Linux可是GNU的大项目)。然后Linux社区坚决反对通过修改代码来适应新的编译器。他们认为这个编译器是坏的。它有猫病。最后,在3.2版本这个时间点左右。Linux社区有动静了。他们决定以GCC 3.2以及更高版本为中心,对Linux进行了更新。

而另一边微软的策略是:如果可以,就尽量不重写。在极端情况下,他们使用了一些特殊方法来决定是否要处理一步或两步名称查找。整个工作接近完成。但在2010年左右写的一批库,证实了根本没有办法让所有库正常地工作。所以微软最终重写了它们的解析器。然后在2018年发布了更新的版本。但是很多人根本没有意识去开启新的解析器。直到2019年,新的解析器才在新的项目里被默认开启。

让我们再回到2011年,还有一件大事发生 ———— C++11标准发布了。记得在C++98,主要的新特性被提出并进行了开发。但是由于一些特性无法按预期地那样正常工作,导致新的C++标准的发布从2006年推迟到了2009年左右。那段时间大家都在尝试让这些新特性正常工作。终于在09年他们移除了C++98的一些东西,然后剩下的特性也进行了各种修复,C++98版本在那时候得到了更新(更新成C++11)。于此同时,C++11又有一大堆新的特性和更强的库。编译器作者们又开始缓慢地追赶。到2013年年末,大部分的编译器才可以编译大部分的C++11代码(还有一些特性没有实现)。

C++委员会从以前的失败中学到了教训。现在有一个激进计划,每三年创建一个新版本。该计划是在第一年内对新功能进行构想和测试,在第二年将其很好地集成,并在第三年达到稳定并正式发布,每三年重复一次这一过程。C++11就是第一个先例,C++14是第二个版本。值得称赞的是,C++委员会按照他们说的做了。在C++14中,他们在C++ 11之上进行了重大更新,并使C++ 11的功能比以前更加有用。在C++14中,先前大多数我们关心的特性的限制都已经被实现了,一些限制变得可以接受,尤其是那些围绕constexpr(常量表达式)的限制。

那些仍然想让所有的C++11特性运行良好的编译器作者现在意识到他们可能要调整计划或者暂时停滞不前了。截止到2015年几乎所有编译器都支持C++14。这个成果可以说是令人泪目的。考虑到前面C++98或C++11时候发生的糟糕状况。

此外,许多编译器作者也参与到C++标准委员会里面。这是因为在新标准出来以前,如果可以提前知道有什么新的特性,就可以让你的编译器率先支持它。而且如果编译器作者发现有什么确定的特性和现有的编译器设计不匹配。那么他就可以去影响C++委员会,让这个特性变得更容易去实现。从而使人们更快地用上新的编译器。

C++正经历重生。这一时期从2011年开始,当时C++ 11面世。一种被称为现代C++(Modern C++)的编程风格被采用。到目前为止,现代C++突飞猛进,因为所有从C++11提出来的点子都在14或17版得到完善。而且现如今所有编译器都完整地支持所有你想要的功能。更好的是,C++ 20很快就会发布,而现在所有编译器的最新版本中就已经支持C++20版中的大部分主要功能了。非常Amazing。

现在,开发人员可以直接学习现代C++。而不用经历先学习C语言,再从C++ 98学到C++ 11,然后再摒弃C++ 98中已经被修正了的部分的学习路径。大部分C++课程或书籍都会尝试去介绍C++的历史。因为这样可以帮助我们更好地理解C++有些地方为什么会那么怪诞。但你不一定非得需要去了解这段历史才能学习C++。现代C++允许你直接跳过这段历史,只需要了解基本的C++原理就可以编写设计良好的程序。

人类积累了几十年的C++知识和经验,像是专门为你们准备的礼物(雾)。现在就是学习C++最好的时机。CPP