专栏名称: 图灵访谈
对话知名作译者,讲述精彩技术人生。你听得见他们,他们也听得见你。
目录
相关文章推荐
新浪科技  ·  【#DeepSeek下周开源5个代码库#,每 ... ·  昨天  
新浪科技  ·  #谁是AI竞赛新王# ... ·  昨天  
51好读  ›  专栏  ›  图灵访谈

访谈 | C++之父Bjarne Stroustrup: 简单的表述方式才是最优的方案

图灵访谈  · 公众号  · 科技媒体  · 2016-12-31 15:35

正文

◆ ◆ ◆ ◆

2

1

7

在这辞旧迎新的日子里,图灵访谈给各位小伙伴儿献上特大彩蛋!借用Bjarne大师的话“趁你还足够年轻的时候,喜欢上某些学科,选择具有挑战性和感兴趣的工作并养成良好的习惯!”,预祝你们在2017年找到新的方向!

◆ ◆ ◆ ◆


Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)



1982年,贝尔实验室(美国AT&T公司)的Bjarne Stroustrup博士在c语言的基础上引入并扩充了 面向对象 的概念,发明了新的程序语言C++。之所以被命名为C++,是为了表达该语言与c语言的渊源关系。Bjarne Stroustrup博士因此被尊称为“C++语言之父”。

之后,面向对象的编程思想开始席卷整个开发领域,标准模板库(STL)和微软的VC++平台推波助澜,C++开始流行起来。可以说,C++对整个软件开发及IT业的贡献,不言而喻。

C++仍在它擅长的领域发挥着不可或缺的作用。作为C++之父,Bjarne Stroustrup也一直致力于C++标准的改进和推广,其著作《C++编程语言》《C++的设计和演化》和《C++加注参考手册》等已成为C++学习的经典读物。

◆ ◆ ◆ ◆

访谈内容

除了作为编程技术大师为人熟知以外,Bjarne还有很多至理名言被大家广泛引用。在观看之前的访谈时,我也被您发人深思和辩证的思维所折服。如何做到将自然语言和编程语言运用如此得体的高度呢?

我想要直接而简洁地表达观点,虽然并不总能成功,但这值得一试。记住,当你写代码的时候,并不仅仅是给编译器看的。相反,代码的“消费者”包括所有阅读和维护代码的人。如果你的代码丑陋不堪、难以理解,它将无法运行甚至造成巨大的维护问题。因此,无论是代码还是“普通文本”,其目的都是清晰地表达观点,帮助其他人理解这些想法。写作是一种缕清思路的方法——对自己和他人来讲,都是。

◆ ◆ ◆ ◆

我记得,您曾经讨论过人们对C++的误解(要理解C++,首先要学习C语言;C++是一种面向对象的语言;可靠的软件需要垃圾回收机制;为了提高效率,必须编写低级代码;C++只适合大型复杂的程序)现在,他们的偏见有所改观吗?

一些人了解了,还有很多人没有。这些谬见普遍充斥于网络上、文章和教科书里,通常被理所当然地接受——即便没有证据支撑仍然被当作事实陈述。所以,很难进行反驳。相信这些谬见的人并不认为自己对C++持有偏见,他们认为自己是进步的,甚至因为这些观点变得优越。

我想借此机会,鼓励大家花点儿时间(重新)审视下自己的观点,同时简单陈述下我自己的一些观点。如果想从技术角度了解详细的论证过程,请参考我的相关论文和书籍。

要理解C++,必须首先学习C语言。 不是的,如果你本身已经是一名程序员了,完全可以直接进入类设计和使用各种库。如果你刚开始接触某种语言,能够处理低级的编程问题,可以依赖C++的强类型检查和各种库更容易、更快速地掌握基础知识。编程新手在使用低级工具(指针、数组、malloc()或free()、casts、宏)的时候,没有理由马上就了解它们存在的问题。复制或比较C语言里的字符串对于编程新手来说是痛苦、单调乏味的。

当然了,不了解指针、数组、自由存储管理(动态内存管理、堆)等方面的知识,就不能在C++上有所建树,但可以之后再学习,等掌握了编程常识和C++基础以后再学习。基于这样的想法,我为大学新生(一年级学生)设计了一套课程并编写了相应的课本:http://www.stroustrup.com/programming.html 。效果很好。

C++是一种面向对象的语言。 不是的,对于大多数包含继承性的传统意义上的OO来说,不是这样的。C++确实支持面相对象编程技术,也相当优秀,但这并不是C++的全部。现代C++,包括大部分的ISO C++标准库,更多地不再遵循这种模式。C++开始使用简单的具体类型和独立函数,并且仅在应用程序域采用分层架构、需要运行时调用的时候才使用运行时多态性。大多数受欢迎的C++应用程序使用了很多技术,并不仅仅是传统的面向对象技术,有时候甚至根本没有采用面向对象技术。

可靠的软件需要垃圾回收机制。 不是的,GC有时会阻碍可靠性的达成。GC并不能消除所有的内存泄漏,不能解决非内存资源的管理问题。泄漏套接口、文件句柄、线程和锁,可能比内存泄漏更容易让系统停止。支持可靠性的最好办法是,找到应对资源管理和错误处理的方法,比如C++提供的RAII (Resource Aquisition Is Initialization, 资源获取即初始化)。我目前正在研究这种方法,为资源安全和类型安全的C++提供一套全面的系统:Http://www.stroustrup.com/resource-model.pdf 。核心观点是保证没有泄漏,使垃圾收集器没有必要存在。在不影响程序员用代码简单、直接地表达想法的前提下,保证没有泄漏确实很难,但不是没有可能。

为了提高效率,必须编写低级代码。 不是的,现代C++十分擅长低级优化和不同抽象层次间的优化,多少数量的代码都无法跟这种能力相比,特别是现代架构具有深度缓存层次结构和配有大幅度指令调序的优化器的情况下。从更高的层面说,人类无法通过直接使用线程和锁,得到最优化的结果,所以我们需要更高级的模型和算法,获得正确性、可靠性、可预测性和原始性能。当关于机器、数据或算法的一些观点被证明是毫无根据的时候,摆弄bit、byte和指针这些基础会变得可悲。举个列子,看一下我和别人合著的这篇文章(http://www.stroustrup.com/improving_garcia_stroustrup_2015.pdf )。文章通过去掉精心设计的优化,提高了spec-mark程序的性能。最后生成的程序变得更精简、更清洁、易于维护、可扩展,而且没有副作用。关于零开销抽象(zero-headed abstraction)的问题,我已经谈了很多。最近,我还看到了很多负开销抽象(negative-headed abstraction)的示例:通过简化适当的抽象获得最优化程序。

C++只适合大型复杂的程序。 不是的,除非你认为一两页代码的量就算是大型、复杂的项目。要知道,任何有影响的程序都需要用到一个或更多个库。这适用于每一种语言。不用任何库,单凭光秃秃的语言,对于编程人员来讲是痛苦的也是徒劳的。

在讨论C++或是(更糟地)下定结论时,随随便便地坚持某种谬见,不加思考,只能说明懒惰。之前,我也写了一篇澄清这些谬见的文章:www.stroustrup.com/Myths-final.pdf 。

◆ ◆ ◆ ◆

C++并非静止不前的。标准委员会很快就将宣布C++17的新增特征。您认为哪些特征是值得期待的?

C++17新增了很多小的改进,对于每一个程序员来说都值得期待,但不要指望特别重大或是颠覆性的改进出现。预计在2017标准发布后,这些新增特征随即可以在所有主要的编译器里应用。事实上,大多数C++17的特征已经能用了。

新增特征不一定对所有人有帮助,大多是为了特定群体的需要而完善C++或是标准库的。你可以在搜索引擎里输入C++找到新增特征的详细列表,不过,我会在这里简要谈几点我所喜欢的特征:

  • 结构化绑定:使用C++17,我们可以打破结构,为结构成员命名。例如:


 mapint,string>mymap;   //...  
 auto[iter,success]=mymap.insert(value);   if (success)f(*iter);  

对于 map insert() 返回 pair ::iterator,bool> ,现在我们可以命名两个返回值并直接使用,而不用创建一个pair对象,再访问它的成员。

对于循环控制,这一点特别有用:

 for(const auto&[key,value]:mymap)  
      coutkeyvalue\n’;
  • 我们用 std::variant 让union的显式使用变得冗余。现在,我们可以


 variantint,double>v;       //可以是int或是double  
 v=12;   auto i=get(v);         




    
//i 变成了12  
 auto d=get(v);      //会抛出bad_variant_access异常
  • 对于之前没有定义求值顺序的情况,现在,多数情况下是可以定义的。 例如

count

可以保证输出 g(y) 的值之前先输出 f(x) 的值。在C++17之前, f(x) g(y) 是可以交错的,这容易产生bug和混乱。

到2020年,我们将看到进行了重大改进的C++20。例如,

  • 概念 ——显著简化、更好指定的泛型编程

  • 模块 ——更好的模块化、更快的编译

  • 协同程序 ——更加简单、快速的生成器和pipelines

  • ——简单、更快、更灵活的网络

  • 新版STL ——更快、更简单、更灵活的算法和ranges

这并不是科幻小说里的幻想,很多特征已经开始在某些领域应用了。问题是,ISO C++标准委员会能否通过。

◆ ◆ ◆ ◆

是否可以用某个新增的特征为例,向我们展示一下该特征是如何符合C++的演化原则(直接硬件访问;零开销抽象;静态类型)的?

提高硬件访问能力和低级代码的性能,是一项需要付出长期努力的任务。有些努力是看得见的,有些不容易看得到。

  • 我们一直在努力提高编译时的计算能力,constexpr是这方面的典范。使用constexpr,我们可以指定一个函数在编译时取值,如果用常量表达式作为参数的话。同样,我们也可以确保编译时就完成某项计算工作。

 constexpr int isqrt(int n)     //编译时取值为常量参数  
 {  
     int i=1;  
     while(i*in) ++i;  
     return i-(i*i!=n);   }   constexpr int s1=isqrt(9);    //s1是3  
 int x;                        //不是常量  
 //…  
 constexpr int s2=isqrt(x);    //编译时,出错  
 countweekday{jun/21/2016}\n’; //星期二  
 static_assert(weekday{jun/21/2016}==tue);  

Constexpr配合使用const可以有效地提高性能,减少代码大小,并提升代码、ROM中数据处理的能力。因为“你不能对常量创造某个竞争状态”,所以有助于并发系统。

  • 另一个不太明显的例子是,C++17确保多数情况下的复制省略。它让我们可以从函数中方便地得到值。例如

 T compute(S a)
 {
     return complicated_computation_yielding_a_T(a);
 }
 T t=compute(s);

这里没有副本!能让我们从指针和动态内存中解脱出来,因为在现代的硬件访问中,间接和动态内存越来越昂贵(相对地)。如果和之前讲到的结构化绑定结合使用,会更有趣

 pairT,T2>compute(S a,S2 b)
 {
     return{ comp1(a




    
,b),comp2(a,b) };
 }

 auto[foo,bar]=compute(s,s2);

同样,这里不需要复制。

在过去的二十年里,模板一直被认为是零开销抽象的,得到了迅猛的发展。它被广泛复制于其它的编程语言中,但通常并不灵活,也不如C++模板运行时的效率。但是,模板基本上会提供编译时的duck typing,而不是基于检查接口的程序;它们会在之后的实例化阶段进行类型检查。因此,模板的迅猛发展导致了相当复杂的编程技术问题。我们需要让泛型代码更接近于非泛型代码,更容易编写,更易于编译器检查同时不影响或限制表达性。

Contexpr 函数的功能包括:不再需要模板就可以得到编译时计算的值。如果你只需要某个类型的值,函数就能很好地表达。使用contexpr,编译时函数就能像其它函数一样,类型检查也能像其它函数的一样(不同于宏技巧或是传统模板的元编程)。

“概念”是支持模板接口规范的语言特征。遗憾的是,它没能成为C++17的新增特征,但作为ISO 技术规范已经应用于GCC6.2了。概念可以解决模板的很多问题。考虑一下 advance() ,简化版的标准程序库函数,它允许迭代器向前移动n个元素。假如我们需要两个版本,一个用于列表之类的东西,每次移动一个元素、操作n次;一个可以直接移动n个元素:

 templateInput_iterator Iter>
 void advance(Iter p,int n){while (—n)++p;}

 templateRandom_access_iterator Iter>
 void advance(Iter p,int n){p+=n;}

也就是说,如果参数是一个随机访问的迭代器,使用第二种快速的版本;否则,使用第一个慢版本。

 void(vector::iterator pv, list::iterator pl)
 {
     advance(pv,17);          //fast
     advance(pl,17);          //slow
 }

这是优化后的快速方案,我用短短几分钟就能向新手解释清楚。它跟“传统模板编程”不同,在编写方式和检查方式上都不同。如果愿意,我甚至可以进一步简化adavance的定义:

 void advance(Input_iterator p, int n){while(n--)++p;}
 void advance(Random_access_iterator p, int n){p+=n;}

这完全符合我们谈论代码的方式,任何一个天真的程序员也会相当合理地这样认为。

最近,我写了一篇文章(www.stroustrup.com/good_concepts.pdf)详细解释了concepts的观点。

◆ ◆ ◆ ◆

某种程度上讲,C++对专家更友好,只有少数的专业人士才能很好地掌握C++。如何减少初学者的困难呢?

“只有少数的专业人士能够很好地掌握C++”夸大了C++的难度,因为确实有数以百万的程序员们用C++编写出了优秀的系统。但坦白说,很多C++代码并不符合专业质量的要求,我们还能做得更好。

C++让编程专家很容易编写出复杂、高性能、低资源消耗的代码,但不足以成为广大普通程序员喜爱的语言,它需要简化。

我努力说服ISO C++标准委员会的专家还有许多的编程教师,说明我们需要不断的努力,开发和讲授更简单的方式,不能仅仅专注于最优化和最聪明的技巧。通常情况下,简单的表述方式才是最优化的方案,“聪明”的技巧对于读者、维护人员、优化器来说可能是一种负担。在谈论用代码表达思想的时候,我大多会用“聪明”表示“太复杂”。最好把聪明用在分析问题和找寻根本办法上。

“用简单的方案解决简单的事情。”之前,C++98 标准模板库采用for语句来控制循环执行:

 for(vector::iterator p=v.begin();v!=v.end();++p)
     coutp\n’;

在C++11中,我们使用range-for-statement :

 for(auto x:v)






请到「今天看啥」查看全文