业界对C++语言的相关评测
2010-07-30 14:20:17 阿炯

谷歌高管对Java与C++的复杂性不满

Linux之父炮轰C++-糟糕程序员垃圾语言

C++代码整洁之道

C++20 标准草案全票批准通过

历时近半个世纪的C++为什么依旧流行

C++之父Bjarne Stroustrup在2022年的访谈录

C++为TIOBE 2022年度编程语言

LinuxC与C++开发后台服务知识架构体系

关于是否将Linux内核从C语言转换为现代C++语言的讨论再次被提及


1.C/C++语言和设计模式
基本C/C++语法、数据类型、数组、指针、结构体、链表、文件操作、队列、栈等。

2.操作系统基础
Linux操作系统的概念、安装方法,线程、进程;
详细了解Linux下的目录结构、基本命令、编辑器VI、编译器GCC、调试器GDB和Make项目管理工具,Shell脚本编写等知识。

3.Linux系统编程与linux操作系统
重点学习标准I/O库,Linux多任务编程中的多进程和多线程;
进程间通信(pipe、FIFO、消息队列、信号量、共享内存、信号)同步与互斥,对共享资源访问控制等重要知识;
提升对Linux应用开发的理解和代码调试的能力;
Linux文件系统。

4.网络原理与网络编程
TCP/IP协议、socket编程、TCP网络编程、UDP网络编程、HTTP;
重点学习网络编程相关API;
熟悉HTTP协议及其实现方法;
对比网络框架,做源码分析,如ACE/ICE/ZMQ/Libevent/Muduo等。

5.数据库、中间件
Mysql、Redis、Nginx、Mongdb等中间件。

6.集群与分布式架构

谷歌高管对Java与C++的复杂性不满

谷歌高管Rob Pike 在OSCON开源大会上打开了简化式编程语言新议题。

现今的商业级编程语言--尤其是C++和Java--太过复杂而不能与今日计算环境充分相容。谷歌资深工程师Rob Pike 在周四于OReilly开源大会上的一次谈话中发表了以上论点。我觉得这些语言太难用了,太精细,太复杂,太冗长。而且这些缺点似乎在与日俱增,Pike说,它们被过度接受,被用得太广了。Pike详细说明了此类语言的缺点,以此展开描述了他和其它谷歌工程师对所开发的名叫Go的新编程语言所持的期望。为了证实此类语言的复杂性,Pike展示了一些C++代码示例,其中一例是一个几乎占据了屏幕整行的变量声明。

我们怎么能让这种东西成为在学校里教、在产业里被使用的操控计算机的标准方式?他问道。这种语言太官僚了(制度化)。每一步都必须要考虑编译是否可通过,尽管Pike承认他自己有点爱开玩笑,他声明说此类问题确实应该被提及。C++出现是因为人们对使用低级语言C绝望,Java出现是为了简化C++。随着时间的推移,新的特性都加在了新出现的二者之上,使它们越来越复杂了。成熟(复杂)会带来噪音(不可预知不被期望的错误),他补充说。Pike还说,此类语言还是在多核处理器和网络被广泛应用等大的事物出现之前被开发出来的,因此它们不能简单地与这些新环境相容。

Pike并不是谷歌唯一一个表达对传统商业级编程语言不满的人。在上个月的USENIX 年度会议上,Gmail工程师Adam de Boor 出语惊动了与会者。公司的Gmail服务完全是由JavaScript写的,总代码全长443,000行,全部手写。他指出,尽管Java更具有表达性,它也更加繁琐。在这个节骨点上,对我来说所使用语言的选择问题是一个大问题,de Boor说。JavaScript是为避免C++和Java不断增长的复杂性,而在过去十年里被开发出来的一批语言中的一支。其它支还包括Ruby和Python。但是尽管有了一个简化了的语法,这类语言也同样有它的弱点,他论证道。

这类新语言要慢一些,伸缩性较差,还隐藏了更多的错误,Pike详尽地描述道。这类语言多为解释型而非编译型,它意味着用这类语言写的程序在运行前是没被编译过的,因此运行的缓慢得多。它们也倾向于使用动态数据类型,即程序员无需定义他们变量所属的数据类型。

动态数据类型并不见得好。本来你可以在编译时找出的错误的,它(动态数据类型)让你只能在运行时找出错误,借着这些观点,Pike接下来描述了作为融合两类语言集各自优点的一个大胆尝试--Go语言。Go是把静态数据类型语言的安全与效率和动态数据类型解释型语言的方便与轻松结合起来的一个尝试,他说,它到底能做多少,还得你亲自去尝试了才知道。

与会人员Larry Augustin,客户关系管理软件提供商SugarCRM的CEO对Pike所指出的C++和Java变得太复杂了的观点表示赞同,尽管他也说这是在一切为应对广泛应用需求而发展的语言身上所发生着的。这些语言在复杂度上增长的原因是,它们用得越多,我们就会发现越多的错误和二义性,然后为消除这些错误和二义性而做的工作又创造出了一些更复杂的东西出来,具有软件工程和程序语言设计背景的Augustin这么说。

我很欣赏你们的目标,他谈及Pike等人的努力时这么说。问题是他能否达成那一目标,抑或是在被越来越多人用之后,它也会变得很复杂,Augustin说。Joab Jackson 负责为IDG News Service报道企业软件和一般技术突破的新闻。可以在推客上跟随他:@Joab_Jackson,他的电子邮件地址是:Joab_Jackson\@idg.com

Linux之父炮轰C++-糟糕程序员垃圾语言

2010年6月,Linux内核的创始人Linus Torvalds最近在一封邮件中说明了内核开发需要使用C语言而非C++的理由。在庞大的项目中,人们对不是自己开发的模块并不了解,能快速理解其他模块中函数的确切含义才能提高开发效率,而C++引入的各种抽象则使代码变得晦涩难读。

另外Linus还认为C++的内存管理方式很弱智(that idiotic "new" keyword in C++)。邮件最后的总结是:很显然C并不适合所有项目,不过C++?还是算了吧。垃圾回收和并发等等,这些才是真正重要的特性。

那么他所期待的C++替代会是什么呢?是方兴未艾的Go语言?还是即将迎来2.0的D语言?或是用 Java,.net这些基于虚拟机的语言去代替?

此文贴出后,引起了大家的较多关注,是意料之中的事情。毕竟C、C++、Linux之父,都是大家最最熟悉的东西。但是许多同学把精力放在纯粹语言优劣的争论上,就没有太大意思了。这场争论的主角之一,微软的Dmitry Kakurin有一句话非常正确:“这是一种信仰问题。因此任何讲道理和争论都会无穷无尽,而且也毫无意义……”

我想,正确的态度应该是从高手们的争论中汲取营养,提高和深化自己对相关语言的认识,结合自己的实际环境,想想应该怎么学,怎么用。

各种语言都是有自己的局限性的,也都有自己最擅长的用武之地。今天软件项目中越来越倾向于采用多语言开发,所以与其花精力做口舌之争,不如多学学对方的语言。写《程序员修炼之路》的Andy Hunt和David Thomas大师早就教导我们,要在软件开发这个行当立于不败之地,应该“每年学一种新的语言”。

Linux之父Linus Torvalds为了Linux内核开发而专门打造的版本控制软件Git已经引起了业界的广泛关注。昨天,有一位Dmitry Kakurin老兄在查看了源代码之后,发现使用的是纯C而非C++,表示不可理解,他直言:“别拿可移植性说事儿,这是屁话 (BS,bullshit)“。(此外,他还批评Git蛮力地直接操作文本,既啰嗦又易错,而且很难跟上高层代码逻辑。)

这个BS引起了Torvalds的强烈反应,他用“*YOU* are full of bullshit.”(你才满嘴屁话呢)作为自己反驳的开场白。接着他先转向了对C++的罕见的火药味十足的炮轰:

“C++是一种糟糕的(horrible)语言。而且因为有大量不够标准的程序员在使用而使情况更糟,以至于极容易产生彻头彻尾的垃圾(total and utter crap)。老实说,选择C就是为了把C++程序员踢出去。……我有这样的结论,任何喜欢用C++而不是C开发项目的程序员可能都是我希望踢出去的人,免得他们来搞乱我参与的项目。C++会导致非常非常糟糕的设计选择。你们这些C++程序员总是一上来就用语言的那些‘漂亮的’库特性比如STL、Boost 和其他彻头彻尾的垃圾,这可能对你们的程序有所‘帮助’,但是却会导致:

——当库无法工作时无穷无尽的折磨(别跟我说什么STL尤其是Boost很稳定而且可移植性很好,那全是屁话,而且一点都不可笑)

——低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。

也就是说,使用优秀的、高效的、系统级的和可移植的C++的唯一方式,最终还是限于使用C本身具有的所有特性。项目限制只用C,意味着参与的人不会捣乱,也意味着会得到许多真正懂得底层问题,而不会折腾那些白痴‘对象模型’垃圾的程序员。

所以,我很抱歉,但是对于Git这样效率是主要目标的软件,C++的所谓优点只是巨大的错误。而我们将看不到这一点的人排除在外却成了一个巨大的附加优势。

如果你想要用C++写的版本控制系统,去玩Monotone吧。他们确实使用了‘真格的数据库’,使用了‘漂亮的面向对象库’、使用了‘漂亮的C++ 抽象’。可是说老实话,所有这些对某些计算机专业人士而言富于吸引力的设计决定,其最终结果确是一堆可怕、难以维护的垃圾。

Torvalds和Dmitry Kakurin争论继续中。

对Torvalds的回击,Dmitry反唇相讥:“随着只用C编程的恐龙们逐渐灭绝,你很快就会发现只剩下自己一个人在固执己见。用Git贡献者的数量是说明不了问题的。显然C++开发者也能够贡献C代码。但是以为他们喜欢这种方式,那可就错了。

“没有C的时候我用汇编编程。然后在C++诞生之前,我转向了C。现在我使用C++和C#,而且不再走回头路。差劲的程序员用任何语言都写不出好程序。但是为了将差劲的贡献者拒之门外这样一个没谱的理由而惩罚优秀的开发者,这简直是胡闹。”

只过了10几分钟,Torvalds就回贴了:“和你不同的是,我实实在在地给出了不喜欢C++的原因,而且指出了它可能导致的各种问题的一些例子。而你呢,没有给出一条像样的使用C++的理由。事实上,Git比其他软件配置管理软件都要好,而好的品味(taste)和C正是原因之一。”

对上面的最后一句话,Torvalds后来又做了如下补充:

说得更具体一些:
——简单和清晰的核心数据结构, 非常精益(lean)且颇具雄心的代码管理着它们,将“简单胜于花哨”

这一方法发挥到极致。
——有意识地不抽象数据结构和算法,因为它们恰恰是Git核心的全部要素(whole point)。

如果你想用更花哨的语言,C++绝对是最糟糕的选择。如果想要真正的高级特性,那就选择有垃圾回收或者好的系统集成的,而不是既缺乏C的简约(sparseness)又缺乏C的直接而且没有重要概念的高层 绑定(high-level bindings to important concepts)的东西。

一言以蔽之,C++正处在困境当中,它既无法帮助原型化或者简单的GUI编程足够简化从而真正可用,又不是C那样积极地鼓励你使用简单和直接的语言构造的精益系统编程语言。

另一位同学插了一句:这还没有提到很难找到两个C++编译器支持同样的特性。

“这与什么恐龙毫无关系,好的品味永远不会过时。将C与汇编语言相提并论,恰恰说明你对自己所讨论的问题缺乏起码的概念(don't have a friggin idea)。”

C++代码整洁之道


整洁的代码在团队中无疑是很受欢迎的,可以高效的被其它成员理解和维护,本文参考《C++代码整洁之道》和《Google C++编码规范》,结合自己的一些想法整理如下:C++本身作为面向对象语言,首先介绍下面向对象一般涉及到的开发原则。

面向对象开发原则

依赖倒置原则:针对接口编程,依赖于抽象而不依赖于具体,抽象(稳定)不应依赖于实现细节(变化),实现细节应该依赖于抽象,因为稳定态如果依赖于变化态则会变成不稳定态。

开放封闭原则:对扩展开放,对修改关闭,业务需求是不断变化的,当程序需要扩展的时候,不要去修改原来的代码,而要灵活使用抽象和继承,增加程序的扩展性,使易于维护和升级,类、模块、函数等都是可以扩展的,但是不可修改。

单一职责原则:一个类只做一件事,一个类应该仅有一个引起它变化的原因,并且变化的方向隐含着类的责任。

里氏替换原则:子类必须能够替换父类,任何引用基类的地方必须能透明的使用其子类的对象,开放关闭原则的具体实现手段之一。

接口隔离原则:接口最小化且完备,尽量少public来减少对外交互,只把外部需要的方法暴露出来。

最少知道原则:一个实体应该尽可能少的与其他实体发生相互作用。

将变化的点进行封装,做好分界,保持一侧变化,一侧稳定,调用侧永远稳定,被调用侧内部可以变化。

优先使用组合而非继承,继承为白箱操作,而组合为黑箱,继承某种程度上破坏了封装性,而且父类与子类之间耦合度比较高。

针对接口编程,而非针对实现编程,强调接口标准化。

C++开发原则

通过上述面向对象开发原则的理解可以细化到具体C++开发原则。

保持简单和直接原则(KISS, Keep it simple and stupid):保持代码尽可能简单,如果需求需要的话,才在代码中引入灵活的可变点,只添加那些可使整体变得更简单的局部复杂的东西。

不需要原则(YAGNI, You're not gonna need it):总是在你真正需要的时候再实现他们,而不是在你只是预见到你将来会需要他们而去实现,在真正需要的时候再写代码,那时再重构也来得及。

避免复制原则(DRY, Do not repeat yourself):不要复制,不要重复,这是相当危险的操作,你修改一处代码的时候总能记得去修改另外一处或另外多处你曾经复制的代码吗?

信息隐藏原则:一段代码调用了另外一段代码,调用者不应该知道被调用者代码的实现,否则调用者就有可能修改被调用者的实现来实现某些功能,而这有可能引发其它调用者的bug。

高内聚低耦合原则:类似单一职责原则,明确每个模块的具体责任,尽量少的依赖于其它模块。

最少惊讶原则:函数功能要与函数名字功能一致,难道你要在一个getter()函数去更改成员变量的值吗?

更干净原则(自命名):离开露营地的时候,应让露营地比你来之前还要干净,当发现代码中有需要改进或者风格不好的地方,应该立刻改掉,不要care这段代码的原作者是谁,也不要care这是谁的模块,代码所有权是集体的,每个团队成员在任何时候都应该可以对任何代码进行更改和扩展。

注重单元测试

重要性就不多说了,防患于未然,构建大型系统尤其需要进行单元测试,保证代码质量,可以防患于未然。一般都讲究测试驱动开发,开发一个功能首先要想好怎么测试,先把测试代码写好,再去开发对应的需求。通过单元测试也有利于开发者更好的进行接口的设计,主要说下良好的单元测试的原则。

单元测试的原则

保证单元测试的代码的质量,单元测试的代码也是代码,不应该和产品代码区别对待,而且单元测试的代码再写出bug更影响测试效率。

单元测试的命名,每个测试单元需要根据具体测试内容进行相应的命名,方便定位分析问题,好的命名如果出现问题时通过测试单元的名字基本就可以定位问题。

保证单元测试的独立性,每个测试单元都是独立的,不依赖于其它测试单元,不要构建测试单元的上下文,上面的测试单元出问题影响到下面的单元测试的设计是很不友好的。

尽量保证一个测试单元使用一个断言,保证测试单元内部的一个相对独立性,上面的断言阻碍了下面的断言测试也是不好的设计。

保证单元测试环境的独立,保证每个测试单元都有独立的环境,不依赖于其它环境,每个测试单元都要是个独立的可运行的实例,每个单元测试结束后记得清理环境。

没必要对第三方库和外部系统做单元测试,只对自己写的代码进行测试。

单元测试尽量不要涉及数据库,数据库的状态是全局的,测试不能保证独立性,而且数据库的访问也是缓慢的,影响单元测试的速度,如果真的需要可以模拟数据库在内容中进行测试,其实通常是在系统集成和系统测试级别时去测试数据库。

不要混淆测试代码和产品代码,产品代码中不应依赖测试代码。

测试必须要快速执行,确保秒级别,大型系统的单元测试也就几分钟而已,单元测试不要访问数据库、磁盘、网络等外设。

找一些测试替身,例如有些数据需要通过网络获取,那可以利用依赖注入做一个网络替身的类模拟这些数据的产生,可以研究研究Google mock。

良好的命名

无论是什么语言,函数和变量的良好命名都是很有必要的,通过函数的名字我们就可以知道这个函数里代码的作用,而不是通过写注释,个人一直倾向于用代码自解释。

文件命名:文件名字要全部小写,中间用_相连,后缀名为.cc和.h

类型命名:类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.

变量命名:不要将变量的类型在名字中体现,这样以后变量类型改变的话还需要去改动变量名,充分利用IDE的功能,变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.

class TableInfo {
...
private:
string table_name_; // 好 - 后加下划线.
string tablename_;   // 好.
static Pool<TableInfo>* pool_; // 好.
int i_table; // 不好,不要将变量的类型在名字中体现
};

常量命名
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 'k' 开头, 大小写混合
const int kDaysInAWeek = 7;

函数命名
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().

枚举命名和常量一致
enum UrlTableErrors {
  kOK = 0,
  kErrorOutOfMemory,
  kErrorMalformedInput,
};

Tip:除非像swap函数里tmp那种一目了然,否则不要搞无意义的命名,函数名变量名字宁可特别长也要写清楚究竟是什么意思,不要用缩写,一个变量尽量在临近使用前才定义,可读性强也可更好利用cpu cache。

编辑器
团队可以统一使用相同的编辑器,个人目前使用的是VS Code编辑器,同时每个项目使用统一的.clang_format文件,统一规范代码格式,所有的换行符都要用LF格式,不要用CRLF格式,在右下角可以设置。

个人的.clang-format文件如下,是在google风格的基础上做了些修改:
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 120
SortIncludes: true
MaxEmptyLinesToKeep: 2

C++编码规范要点小总结

每个头文件都要使用#define避免被重复引用

命名格式 <PROJECT>_<PATH>_<FILE>_H_

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

或使用#pragma once,而#define方式更通用

鼓励在 .cc 文件内使用匿名命名空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径。禁止使用 using 指示,禁止使用内联命名空间(inline namespace)  

一行尽量不要超过120个字符,一个函数尽量不要超过40行,同时一个文件尽量控制在500行内.  

所有的引用形参如不做改动一律加const,在任何可能的情况下都要使用 const或constexpr

new内存的地方尽量使用智能指针,c++11 就尽量用std::unique_ptr替代std::auto_ptr

合理使用移动语义,减少内存拷贝,参考左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里

禁止使用 RTTI,尽量在编译期间就确定参数类型,不要搞运行时识别typeid这种代码

使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式

明确使用前置++还是后置++的具体含义,如不考虑返回值,尽量使用效率高的前置++ (++i)

不要使用uint类型,如果需要使用大整型可以考虑int64,否则类型的隐式类型转换会带来很多麻烦

如无特殊必要不要使用宏,可以考虑使用const或constexpr替代宏,宏的全局作用域很麻烦,如果非要用在马上要使用时才进行 #define, 使用后要立即 #undef

google文档说一定不要用宏来控制条件编译(但是我自己还没有查到不用宏如何控制条件编译,或许就不要搞条件编译)

尽可能用 sizeof(varname) 代替 sizeof(type).使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新. 您或许会用 sizeof(type) 处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式,这时用变量就不合适了

类型名如果过长的话可以考虑使用auto关键字

注释统一使用 // ,不要通过注释禁用代码,擅用git,不要为易懂的代码写注释

写完代码后记得format,VS Code(windows快捷键) shift + alt + F ,每个项目最好都有统一的.clang_format文件

使用C++的string和stream替代C语言风格的char*,使用std::ostream和std::cout替代printf()、sprintf()等

尽量使用STL标准库的容器而不是C语言风格的数组,数组的越界访问之类当时是不会报错的,反而可能弄脏堆栈信息,导致奇奇怪怪难以排查的bug

可以更多的使用模板元编程,尽量多的使用constexpr等编译器计算,编译器是我们的好搭档,个人认为模板元编程以后会是C++的主流技术

可以考虑更多的使用异常处理方式,而不是C语言风格的errno错误码等,这里可以参考你的c++团队还在禁用异常处理吗?

附:本文不是技术文章,介绍较为主观,可能和很多人想法有所冲突,各位可以结合自己的经历经验酌情参考。

参考资料
C++代码整洁之道

C++20 标准草案全票批准通过

2020年9月4日,C++20 的国际标准草案(DIS, Draft International Standard)投票结束,最终获全票批准通过。这意味着 C++20 已获得了最终的技术批准,并完成了 ISO 的投票,预计在完成最后一轮 ISO 的编辑工作后,C++20 将在2020年底正式发布。与上个版本 C++17 相比,C++20 是一次重大的更新,引入了许多新特性:
模块 (Modules)
协程 (Coroutines)
范围 (Ranges)
概念与约束 (Constraints and concepts)
指定初始化 (designated initializers)
操作符 <=> != ==
constexpr支持:new/delete、dynamic_cast、try/catch、虚拟
constexpr 向量和字符串
计时:日历、时区支持
std::format
std::span
std::jthread

详细变化查看 cppreference.com, open-std.orgWikipedia。像 Concepts/Ranges/Modules/Coroutines 这些新特性将会对开发者及 C++ 生态产生不小的影响,也为这门“高龄”编程语言增添不少魅力。

工具方面,GCC 10 编译器对 C++20 标准已基本支持,在更早的 GCC 8/9 中,目前尚待完成的是对 modules 的支持。GCC Wiki 页面概述了目前正在进行的工作。此外,Clang C++ 状态报告页面显示已完成了对大部分特性的支持,待完成的包括 concepts, immediate functions, modules 和其他功能。

ISO C++标准委员会主席 Herb Sutter 还介绍了 C++23 的相关会议和进度表,从11月开始将开始召开虚拟会议,以正式批准对 C++23 工作文件的更改,包括潜在的新功能。

C++23 进度表和优先级安排:
C++23 schedule (P1000R4)
C++23 priorities (P0592R4)

最后,年度最大的 C++ 会议 CppCon 2020 即将以线上举办的形式开始。

历时近半个世纪的C++为什么依旧流行

2021年7月未消息,尽管自 2001 年以来,C++ 在 TIOBE 编程社区指数榜上逐渐呈现出了一个下滑趋势,甚至有人觉得 C++ 已经像 COBOL 一样“死了”。但 SDTimes 认为,C++ 仍然像以前一样有生命力、有活力且有意义。TIOBE 7 月指数显示,C++ 依旧是地球上第四大最受欢迎的编程语言,占据了近 7.5% 的份额,紧随 C、Java 和 Python 之后。虽然这与 2003 年该语言在 TIOBE 中接近 18% 的峰值相比确实有所下降,但其流行程度仍然是不可否认的。作为一种在首部 iPhone 发布前近十年就已标准化的编程语言,C++  如何以及为何在云计算和高性能计算的时代仍然具有相关性?SDTimes 做出了如下分析:

Still popular, still relevant

C++ 在大众市场上持续存在的核心原因是什么?性能、速度、成熟度等:
性能 — C++ 仍然是高性能软件的黄金标准。Adobe 使用它、Unreal Engine擎基于它、Chrome 和 Firefox 也是如此。
速度 — 在处理速度仍是瓶颈的时候,C++ 依旧被用来创建市场上一些最快的软件。
成熟度 — 很明显,C++ 已经存在近 40 年的事实意味着大多数软件问题已经得到解决。
生态系统 — C ++ 是大多数编程课程的核心部分,拥有大量的开发工具、第三方组件、库、手册等。
嵌入式 — 大多数嵌入式系统,更不用说物联网设备,都是由用 C/C++ 编写的固件驱动的。

Not just legacy

另一方面,也有一些优秀的“老”项目在推动着 C ++ 的持续发展,很多几十年前开始的 C++项目(或从 C++ 前身如 C 开始的项目)至今仍在运行。例如UNIX、甲骨文的 MySQL、Linux 内核、微软的 Office 和Visual Studio,甚至苹果的一些 OS X,都是采用 C++ 编写的。除此之外,C++ 也主导着当今大部分的新开发项目。 C++ 是游戏的主要语言,为 Unreal Engine 和当今许多最大和最流行的游戏提供动力。同时C++还主导着物联网、国防软件和制造业应用;几乎所有被认为是实时的东西都采用了该语言。它也仍然被用于性能密集型工作站应用的新开发(如 CAD/CAM 软件)或基于计算密集型服务器的应用程序(如高频交易)的新开发。它被应用于虚拟机、设备驱动程序、运行时解释器和工具。C++ 还在人工智能驱动应用程序的开发中承担了重任,并且仍然是谷歌 Android 操作系统的核心部分。

The bottom line

进入第四个十年的 C++ 仍在普及。C# 和 Java 等较新的语言将在未来几年继续引领市场,而 C++ 也将继续保持并驾齐驱。C++ 是一种强大且不断发展的语言,它帮助了并将继续帮助创建我们所知的计算基础。C++ 的下一次更新定于 2023 年,它有着庞大的开发者社区和庞大的知识库,更不用说最佳实践、外部库、组件和工具,这些东西在一些新语言中需要几十年才能成熟。C++ 在现代计算中根深蒂固,不会消亡。

C++之父Bjarne Stroustrup在2022年的访谈录

本文为 CSDN 于2022年4月下旬翻译。
作者 | Evrone,已获作者翻译授权 译者 | 弯月

1979年诞生的C++已迈入“不惑之年”,如今却依旧在主流编程语言中占据重要一席。但今天我们不再赘言C++的成功史,而将目光转向C++背后的英雄——C++之父Bjarne Stroustrup。在本节文章中,让我们跟着软件开发商Evrone的采访,共享Bjarne Stroustrup四十余年程序人生中所蕴藏的大量经验财富。

作为最初只是出于改进C语言、增加部分基于面向对象编程功能的C++,它的成功令C++之父Bjarne Stroustrup自身都感到意外:“C++的成功令人惊讶”。不过也是因为C++一直广受开发者喜爱,Bjarne Stroustrup多年来始终坚持在开发一线——目前71岁的他仍在积极参与C++开发工作。经常有受益于C++的开发者说,C++改变了他们,但殊不知,C++改变的还有Bjarne Stroustrup自己。

(图片来自 Bjarne Stroustrup 个人主页)

C++ 对 C++ 之父的影响

Evrone:您创建了一门高效且快速的编程语言,这无疑改变了我们的世界。请问对您个人而言,在创建这门语言的工作中是否也发生了变化?

Bjarne:我从未想过这个问题,不过我认为这个问题可以从两个角度进行回答:哪些方面长期保持不变,又有哪些方面发生了巨大变化。

从很久以前开始,我就对历史、哲学等许多领域都抱有广泛兴趣,多年来也一直保持不变,我认为这对C++的发展具有重要意义。因此,相较于深入研究学术,我更想要建立更多东西,这也给我对工程领域的看法带来了一定“偏见”。我看重性能、低成本和可靠性,再加上我很重视反馈、不断进步以及对现实世界问题的理解,最终造就了C++。

多年来,在我国工作中持续稳步发展的一个方面就是教育。当我试图解释自己的想法时,我逐渐意识到:不能只是做出来,你必须教人们如何使用它。这也是C++一直存在的问题。通常,我的个人看法会被其他人的声音所淹没,这些人往往有着更简单的愿景并提出更大的主张。在20世纪80年代和90年代,我们经常会听到“我们没办法迅速培养教师”之类的抱怨,所以C++的教学常常采用一种在我看来很糟糕的方式,也难怪有些人会对C++抱有非常负面的看法。

如果我能早点知道自己一生中的大部分时间都在说写英语,那么我一定会在英语上更加努力。这些年来,我需要面对并解决许多问题,这些问题种类繁多且范围广泛,在这过程中我对它们也有了很多见解。许多困难的问题都与组织、管理和教育有关。与技术教育相比,广泛的阅读令我更加受益匪浅。我明白了良好的语言设计需要高度谦逊,因为我们不了解的东西太多:这个世界在不断变化,我们的问题在不断变化,而我们自身也在变化。

洞悉行业当前现象及趋势见解

Evrone:强大的C++元编程系统有时会被滥用,开发人员会想方设法将任务从运行时转移到编译时。在您看来,有没有什么可行的解决方法?

Bjarne:每一种新颖且强大的功能或技术都会被过度使用和错误使用,对此我想不到有什么好办法。不过这也会带来一定好处,因为过度使用会让我们认识到弱点,并设法解决。

举个例子,C++模板元编程非常有用,因此许多人愿意忽略它的不足及其负面消息去使用它。后来,我们从中总结了大量经验,利用编译时求值的函数(constexpr和consteval)和概念来弥补相关不足,大大简化了手动编写的大部分代码。

Evrone:业界流传着一个“笑话”,即任何架构问题都可以通过引入新的抽象层来解决,结果却导致抽象层太多。我们看到很多C++代码都有大量的抽象类,那么作为C++创始人,您认为如何才能让每个抽象类都发挥积极作用?

Bjarne:这就是David Wheeler的“计算第一定律”,他是我的论文导师,也是一个了不起的人,我从他身上学到了很多。

你说的这个“笑话”反映了现实,人们确实喜欢将真正的逻辑隐藏在多层接口之后,还会使用大量的间接方式,但这可能会导致代码量和运行时间增加一到两个数量级。因此,现代C++的大多数功能就是为了让人们编写的接口能被编译器优化成更简单的机器代码,尽量减少使用无谓的间接方式。

Evrone:随着DeepMind的AlphaCode神经网络的问世,越来越多媒体声称这种神经网络很快就会取代程序员。您认为这种说法有依据吗?

Bjarne:我不太确定。我也很怀疑人工智能是否真的能够取代我最关心的编程类程序员,但我认为可靠性和最佳性能不太容易被标准化和平均化。AI不是我的强项,但TensorFlow和类似的库都有C++的身影,这也就意味着——不论好坏,起码我已经尽了自己的一份力。

Evrone:在过去十年中,我们看到许多语法糖(对语言的编译结果和功能没有实际影响、却能更方便程序员使用该语言的语法)被添加到主流语言中。似乎通过“臃肿的语法”为开发人员提供更好的工具已成为一种趋势,您对此如何看待?

Bjarne:只要能减轻程序员的负担,即便是“臃肿的语法”也无伤大雅,我更愿称之为“让简单的任务变得更简单”。我认为,关键思想是让程序员能直接在代码中表达基本思想。例如,用C语言风格的循环来表达容器上的简单循环没有任何优点或好处,最好使用range-for或算法。在大多数情况下,这些方法都能直接表达意图,只有在特殊情况下才需要考虑复杂的循环变量,例如在访问容器的元素时,不是挨个访问,而是隔一个访问一个。想法越直接,编写、阅读和维护代码就越容易,并且通常优化也越简单。

因此,我不认为“应该只有一种表达方式”是一种理想的做法,这种思想会导致有些情况很难表达,而有些情况确实需要大费周折才能表达出来。此外,经过长年累月的优化,语言本身也会发生变化。从这些角度来看,编程语言与自然语言并没有什么不同。

开发人员该如何突破困境?

Evrone:如今人们普遍认为,使用现代框架比应用数学知识更重要,您能在这方面给程序员一些建议吗?

Bjarne:程序员在数学上付出的努力永远也不会白费。数学是训练我们大脑的最佳方法之一,尤其是与计算相结合时,我们很快就会意识到自己的错误;数学也教会了我们一丝不苟,不要轻信过于简单或错误的想法。

在很多领域中,数学知识都是必不可少的,例如科学计算、某些图形形式和许多金融软件。但对大多数人来说,数学的关键领域是概率和统计:你的代码速度够快吗?能扩展吗?某些事件的发生概率是多大,影响是什么?当然,也有许多应用程序不需要数学,但在构建基础设施或大规模部署应用程序时,容量和能耗的成本不容忽视,此时数学知识的不足就可能会带来危害。

Evrone:有时作为开发人员,面对手头的编程任务,我们总是找不到合适的解决方案。您有没有遇到过类似情况,可否分享一些处理这类困境的建议?

Bjarne:当然遇到过。在面对新奇或重大难题时,每个人都需要付出巨大的努力,可能几小时、几天甚至更长时间,我们可能还会陷入困境,感到无助和沮丧。

这个时候,我们需要从逻辑层面来看问题。仔细想想你的目标是什么,或许是因为你努力的方向不正确,或许是因为你提出的要求不合理。也可以偶尔休息一下,想想别的事情,我一般会去跑步。放松下来以后,脑海中就会浮现一些有用的想法。

Evrone:业务环境时常要求我们在实现新功能时严格遵守最后期限。在您看来,开发人员要如何在代码质量和开发速度之间保持平衡?

Bjarne:这很大程度上依赖于管理结构和技术文化。我的个人意见(主要基于贝尔实验室的工作经历)是,如果你的组织内人才济济,就不必要求所有人都为下一个截止日期而努力。你需要一些关键人物为未来做策划、开展实验、构建下一个重要系统的第一个版本及后续版本。我认为,一个好的组织需要构建稳定的产品线,其中绝大部分都应在已部署并进入维护阶段的产品上进行革命性变更。不过显然,这种做法与常见的削减成本和发布颠覆性系统的思想背道而驰。

Evrone:许多人将您视为自己的导师,那么在您看来,一位优秀的导师应具备哪些品质?

Bjarne:愿意倾听并认真理解问题。在提供建议时要保持一定的谦虚,因为一般情况下,我们的理解都是不完整的。除此之外,优秀的导师必须给出具体建议,而不是一通模棱两可的废话。如果有人认真向你请教问题,你就应该给出严肃的答案助其进步。但老实说,提供建议也是一件很难的事。

不过教学相长,这也是我们进步的主要动力,一位优秀的导师会从学生身上学到很多东西。

预告C++的未来发展

Evrone:您能透露一下在C++未来的版本中会出现哪些变化吗?

Bjarne:首先,社区必须适应C++20全新、强大并简化的特性,它在C++11的基础之上推出了很多提升。C++ 20有很多语言特性,还有标准库组件之类的,但在这里,我只介绍其中两个语言特性。

模块:我们可以认为,import Mod可以访问由module Mod导出(exported)的接口。这种方式更加整洁,与之相比,#include则会泄露实现细节和宏。此外,模块的编译速度也得到了大幅提升。例如,简单编译一段import std代码的速度比#import快十倍(注意:std包含所有标准库,而eam>不到其10%)。std模块目前仍处于试验阶段,但经投票已被纳入C++23。

概念:在C++20之前,所有模板都是不受约束的,也就是说,它们没有定义接口以供开发人员和工具查看模板的参数要求,但C++20中提出了“概念(Concepts)”弥补这一缺点。我们一直希望实现这种受约束的模板参数,只不过以前我不知道如何在不限制灵活性或增加运行时开销的情况下实现这个想法。现在我们可以检查模板的使用情况,在出错时得到改进后的错误消息,也可以重载函数模板,甚至提升某些方面的性能。

至于之后的版本,受疫情影响,很多功能的开发都被推迟了。我们希望能够在C++23中推出一些重要的功能,但很可惜,我最喜欢的一些功能未能如愿进入C++23。在这里我只透露三点。

静态反射:我们需要一种机制来根据程序中的类型在编译时生成代码,这不仅可以为我们提供运行时反射的灵活性,而且无需时间或空间成本。例如,我们可以针对一组固定类型,很容易地生成优化过的JSON阅读器。在这方面,我们已经付出了巨大的努力。

模式匹配:在许多函数式编程语言中,根据表达式与一组类型或备选值的匹配程度来选择操作,是表达备选操作最方便的方法之一。我们可以在C++中实现相同的功能,且在此过程中无需使用switch语句。我们有一个非常完整的设计和一个实验性的实现,所以我非常期待C++26。

并发模型:多年来,我们一直在研究一个通用的并发模型,可惜我们发现的总是明显不太适合的用例,因此我们不得不再三推迟。但我希望C++26能有所突破。

最后,有一点需要记住:单凭一个功能并不能让编程变得方便、安全且高效。我们需要在类型系统中创建大量特性,同时,我们又不能破坏现有的数十亿行C++代码,因为兼容性和稳定性是非常重要的特性。

原文地址

C++为TIOBE 2022年度编程语言

TIOBE 于2023年1月上旬宣布了 2022 的年度编程语言:C++,因为该语言在 2022 年获得了最多的流行度 (+4.62%);紧随其后的分别是 C (+3.82%) 和 Python (+2.78%)。此前在 2022 年 12 月的榜单中,C++ 曾超越 Java 成为 TIOBE 指数第 3 名。TIOBE CEO Paul Jansen 评价称,C++ 受欢迎的原因在于它作为一种高级面向对象语言的出色性能。因此可以用 C++ 开发快速而庞大的软件系统(超过数百万行代码),而不必落入维护的恶梦。

C++ 崛起的另一个原因是它 “最近” 不断发布具有有趣特性的新语言标准。第一个里程碑是于 2011 年发布的 C++11,这是自 1998 年以来的第一次重大变化。这一新标准的采用花了几年时间,因为没有可用的 C++ 编译器来支持新的语言定义。由于 C++ 11,C++ 在 TIOBE 指数中的地位在经历了 2001 年以来不断下降的趋势之后,开始慢慢走上坡路。第二个里程碑是最近的 C++ 20 ,该版本引入了 modules。这一特性可能会在未来几年进一步提升 C++ 在 TIOBE 指数中的地位。

其他语言方面,C++ 竞争对手 Rust 在 2022 年再次进入了榜单的 top 20(一年前排名第 26 位)。以易于与 C 接口而闻名的 Lua 从第 30 位提高至第 24 位,F# 在一年的时间里从排名第 74 位跃升至排名第 33 位。还有 Kotlin(从 #29 到 #25)、Julia(从 #28 到 #29)和 Dart(从 #37 到 #38)等一些有前景的语言,在进入前 20 名之前还有很长的路要走。


C++23是C++20之后的下一个C++标准,它包含了对C++的一系列改进,但对于C++98、C++11或C++20那样具有革命性的影响力较小,更像是C++17的延续。C++23的制定过程非常有趣,包括了很多的讨论和争议。在此介绍于2023年10月发布的C++23的一些新特性。

模块
C++23引入了模块,使得标准库能够作为模块而不是头文件导入。这样可以提高编译速度,减少头文件依赖和名称空间污染。

if consteval
C++23引入了if consteval,如果上下文是常量评估的,则执行语句。这个特性可以用于编译时计算,例如在编译时计算斐波那契数列。

显式this参数
C++23允许非静态成员函数的第一个参数可以是由“this”表示的显式对象参数。这个特性可以用于实现某些元编程技术。

多个参数重载下标运算符
C++23允许使用多个参数重载下标运算符,使处理多维数组更容易。

延长for-range-initializer中临时对象的生命周期
C++23允许延长for-range-initializer中临时对象的生命周期,直到循环结束。这个特性可以用于避免不必要的拷贝和移动。

其他特性
除了上述特性外,C++23还包括了其他一些特性,例如std::format、std::span、std::stop_token、std::source_location等等,其目标能否顺利达成?

C++23的目标包括Library support for coroutines、A modular standard library、Executors、Networking。

C++23的制定过程中存在很多讨论和争议,但最终的技术工作已经完成。C++23的新特性相对于C++20来说影响力较小,更像是C++17的延续。C++23的新特性已经在GCC和Clang中被实现,但实际可用性取决于编译器开发人员。总的来说,C++23引入了一些有用的新特性,但相对于C++20来说影响力较小。C++23的目标包括了一些重要的特性,但能否顺利达成还有待观察。

关于是否将Linux内核从C语言转换为现代C++语言的讨论再次被提及

关于将 Linux 内核转换为支持现代 C++ 代码的前景,一个已有六年历史的 Linux 内核邮件列表讨论再次被点燃。现有的Linux 内核主要由 C 代码和各种手工编写的汇编语言构成,加上 Linux 内核支持 Rust 的工作也在不断增加。虽然目前还不清楚是否有足够的力量将其变为现实,但 Linux 内核社区邮件列表已重启讨论,探讨未来将 Linux 内核 C 代码转换为 C++ 的可能性。早在2018年4月1日,红帽工程师大卫-豪威尔斯(David Howells)就提出了一组 45 个补丁,开始将内核转换为 C++。这将允许主线内核使用内联模板函数、内联重载函数、类继承以及其他目前 Linux 内核的 C 代码不支持的功能。那天很难进行认真的讨论,最终这些补丁在 Linux 内核邮件列表上停留了六年,没有引起太多讨论。

但在2024年1月上旬,长期从事 Linux 开发的彼得-安文(H. Peter Anvin)回应了内核邮件列表的主题。Anvin 写了一篇长长的 LKML 帖子,围绕为什么 Linux 内核的 C++ 最终可能是正确的时机提出了他的理由:"安德鲁-平斯基(Andrew Pinski)最近知道了这个主题。我知道它是在 2018 年 4 月 发布的,要么是个玩笑,要么可能被当成了玩笑。不过,我认为它有其合理性,我将在此尝试激发我的观点。"

自 1999 年以来,C 和 C++ 都有了长足的发展,而事实上,在我个人看来,C++ 终于"长大"了,对于操作系统内核所体现的嵌入式编程而言,它是一种更好的 C 语言。我是作为内核中大量宏和内联汇编Hacks的作者才这么说的。让我这么说的真正原因是,我们最近提出的许多针对 gcc 扩展的要求,其实在标准 C++ 中很容易实现,而且在许多情况下,无需修改全局代码即可改进基础架构。在我看来,C++14 是拥有合理元编程支持的"最低"版本,它拥有大部分元编程支持,却没有早期版本的类型地狱(C++11 拥有大部分元编程支持,但 C++14 填补了一些关键缺失)。在我看来,C++20 才是真正改变游戏规则的主要因素;虽然早期版本可以使用大量 SFINAE hacks,但它们也提供了完全无用的错误信息。C++20 增加了一些概念,这使得真正获得合理的错误信息成为可能"。

对于那些可能会提出"用 Rust 重写 C 代码!"的人,Anvin 在他的信息中主动补充道:"现在为什么不使用 Rust 呢?首先Rust 使用的是不同的语法(在我看来,往往是无端的),不仅所有内核开发人员都需要非常熟悉,才能获得与 C 相同的"感觉",而且将 C 代码转换为 Rust 并不是一件可以零敲碎打的事情,而现有的 C 代码经过一些清理就可以编译为 C++。"

SUSE Lans 的 Jiri Slaby 表示支持 Linux 内核的 C++ 计划。最初发布内核补丁的红帽公司的 David Howells 也表示支持这一讨论。我们将拭目以待 LKML 讨论的结果,以及在 2024+ 年是否最终有足够的动力支持现代 C++ 代码--或者至少是一些定义的 C++14~20 子集--在 Linux 内核中的应用。Linus Torvalds 过去一直热衷于反对 C++,但如果他对最近的 C++ 标准更加满意,或者他仍然坚持使用 C 语言来维护 Linux 内核,那么我们就能看到潮流是否最终转向了。

直到 2022 年,Linux 内核才开始从 C89 转向 C11。特别是如果达成共识,允许在内核中使用 C++14/C++20 编程子集,那么在提高基本编译器要求之前,可能还需要一段时间才能通过,以便推出更广泛的编译器支持。