C++之父Bjarne Stroustrup: 簡單的表述方式才是最優的方案(圖靈訪談)

劉敏ituring發表於2016-12-30

2016年的最後一天,圖靈訪談給各位小夥伴兒獻上特大彩蛋!借用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++學習的經典讀物。

C++之父Bjarne Stroustrup: 簡單的表述方式才是最優的方案(圖靈訪談)

訪談內容:

英文版

除了作為程式設計技術大師為人熟知以外,Bjarne還有很多至理名言被大家廣泛引用。在觀看之前的訪談時,我也被您發人深思和辯證的思維所折服。如何做到將自然語言和程式語言運用如此得體的高度呢?

我想要直接而簡潔地表達觀點,雖然並不總能成功,但這值得一試。記住,當你寫程式碼的時候,並不僅僅是給編譯器看的。相反,程式碼的“消費者”包括所有閱讀和維護程式碼的人。如果你的程式碼醜陋不堪、難以理解,它將無法執行甚至造成巨大的維護問題。因此,無論是程式碼還是“普通文字”,其目的都是清晰地表達觀點,幫助其他人理解這些想法。寫作是一種縷清思路的方法——對自己和他人來講,都是。

我記得,您曾經討論過人們對C++的誤解(要理解C++,首先要學習C語言;C++是一種物件導向的語言;可靠的軟體需要垃圾回收機制;為了提高效率,必須編寫低階程式碼;C++只適合大型複雜的程式)現在,他們的偏見有所改觀嗎?

一些人瞭解了,還有很多人沒有。這些謬見普遍充斥於網路上、文章和教科書裡,通常被理所當然地接受——即便沒有證據支撐仍然被當作事實陳述。所以,很難進行反駁。相信這些謬見的人並不認為自己對C++持有偏見,他們認為自己是進步的,甚至因為這些觀點變得優越。

我想借此機會,鼓勵大家花點兒時間(重新)審視下自己的觀點,同時簡單陳述下我自己的一些觀點。如果想從技術角度瞭解詳細的論證過程,請參考我的相關論文和書籍。

要理解C++,必須首先學習C語言。不是的,如果你本身已經是一名程式設計師了,完全可以直接進入類設計和使用各種庫。如果你剛開始接觸某種語言,能夠處理低階的程式設計問題,可以依賴C++的強型別檢查和各種庫更容易、更快速地掌握基礎知識。程式設計新手在使用低階工具(諸如指標、陣列、malloc()或free()、cast、巨集)的時候,總是會遇到麻煩,沒有理由讓他們遭受這些問題及複雜性。複製或比較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++17找到新增特徵的詳細列表,不過,我會在這裡簡要談幾點我所喜歡的特徵:

  • 結構化繫結:使用C++17,我們可以打破結構,為結構成員命名。例如:

```

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

對於map<int,string>insert()返回pair<mymap<int,string>::iterator,bool>,現在我們可以命名兩個返回值並直接使用,而不用建立一個pair物件,再訪問它的成員。

對於迴圈控制,這一點特別有用:

 for(const auto&[key,value]:mymap)  
      cout<<key<<”->”<<value<<’\n’;
  • 我們用std::variant 讓union的顯式使用變得冗餘。現在,我們可以

```

 variant<int,double>v;       //可以是int或是double  
 v=12;  
 auto i=get<int>(v);         //i 變成了12  
 auto d=get<double>(v);      //會丟擲bad_variant_access異常
  • 對於之前沒有定義求值順序的情況,現在,多數情況下是可以定義的。例如

count<<f(x)<<””<<g(y)<<’\n’;

可以保證輸出g(y)的值之前先輸出f(x)的值。在C++17之前,f(x)g(y)是可以交錯的,這容易產生bug和混亂。

到2020年,我們將看到進行了重大改進的C++20。例如,

  • 概念——顯著簡化、更好指定的泛型程式設計
  • 模組——更好的模組化、更快的編譯
  • 協程——更加簡單、快速的生成器和pipeline
  • ——簡單、更快、更靈活的網路
  • 新版STL——更快、更簡單、更靈活的演算法和range

這並不是科幻小說裡的幻想,很多特徵已經開始在某些領域應用了。問題是,ISO C++標準委員會能否通過。

是否可以用某個新增的特徵為例,向我們展示一下該特徵是如何符合C++的演化原則(直接硬體訪問;零開銷抽象;靜態型別)的?

提高硬體訪問能力和低階程式碼的效能,是一項需要付出長期努力的任務。有些努力是看得見的,有些不容易看得到。

  • 我們一直在努力提高編譯時的計算能力,constexpr是這方面的典範。使用constexpr,我們可以指定一個函式在編譯時取值,如果用常量表示式作為引數的話。同樣,我們也可以確保編譯時就完成某項計算工作。

```

 constexpr int isqrt(int n)     //對於常量引數,能在編譯時求值 
 {  
     int i=1;  
     while(i*i<n) ++i;  
     return i-(i*i!=n);  
 }  
 constexpr int s1=isqrt(9);    //s1是3  
 int x;                        //不是常量  
 //…  
 constexpr int s2=isqrt(x);    //編譯時,出錯  
 count<<weekday{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);

這裡沒有副本!能讓我們從指標和動態記憶體中解脫出來,因為在現代的硬體訪問中,間接和動態記憶體越來越昂貴(相對地)。如果和之前講到的結構化繫結結合使用,會更有趣

 pair<T,T2>compute(S a,S2 b)
 {
     return{ comp1(a,b),comp2(a,b) };
 }

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

同樣,這裡不需要複製。

在過去的二十年裡,模板一直被認為是零開銷抽象的,得到了迅猛的發展。它被廣泛複製於其它的程式語言中,但通常並不靈活,也不如C++模板執行時的效率。但是,模板基本上會提供編譯時的duck typing,而不是基於檢查介面的程式;它們會在之後的例項化階段進行型別檢查。因此,模板的迅猛發展導致了相當複雜的程式設計技術問題。我們需要讓泛型程式碼更接近於非泛型程式碼,更容易編寫,更易於編譯器檢查同時不影響或限制表達性。

Constexpr 函式的功能包括:不再需要模板就可以得到編譯時計算的值。如果你只需要某個型別的值,函式就能很好地表達。使用constexpr,編譯時函式就能像其它函式一樣,型別檢查也能像其它函式的一樣(不同於巨集技巧或是傳統模板的超程式設計)。

“概念”是支援模板介面規範的語言特徵。遺憾的是,它沒能成為C++17的新增特徵,但作為ISO 技術規範已經應用於GCC6.2了。概念可以解決模板的很多問題。考慮一下標準庫函式advance()的簡化版,它允許迭代器向前移動n個元素。假如我們需要兩個版本,一個用於列表之類的東西,每次移動一個元素、操作n次;一個可以直接移動n個元素:

 template<Input_iterator Iter>
 void advance(Iter p,int n){while (n--)++p;}

 template<Random_access_iterator Iter>
 void advance(Iter p,int n){p+=n;}

也就是說,如果引數是一個隨機訪問的迭代器,使用第二種快速的版本;否則,使用第一個慢版本。

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

這是優化後的快速方案,我用短短几分鐘就能向新手解釋清楚。它跟“傳統模板程式設計”不同,在編寫方式和檢查方式上都不同。如果願意,我甚至可以進一步簡化advance的定義:

 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)詳細解釋了concept的觀點。

某種程度上講,C++對專家更友好,只有少數的專業人士才能很好地掌握C++。如何減少初學者的困難呢?

“只有少數的專業人士能夠很好地掌握C++”誇大了C++的難度,因為確實有數以百萬的程式設計師用C++編寫出了優秀的系統。但坦白說,很多C++程式碼並不符合專業質量的要求,我們還能做得更好。

C++讓程式設計專家很容易編寫出複雜、高效能、低資源消耗的程式碼,但不足以成為廣大普通程式設計師喜愛的語言,它需要簡化。

我努力說服ISO C++標準委員會的專家還有許多的程式設計教師,說明我們需要不斷的努力,開發和講授更簡單的方式,不能僅僅專注於最優化和最聰明的技巧。通常情況下,簡單的表述方式才是最優化的方案,“聰明”的技巧對於讀者、維護人員、優化器來說可能是一種負擔。在談論用程式碼表達思想的時候,我大多會用“聰明”表示“太複雜”。最好把聰明用在分析問題和找尋根本辦法上。

“用簡單的方案解決簡單的事情。”之前,C++98 標準模板庫採用for語句來控制迴圈執行:

 for(vector<int>::iterator p=v.begin();v!=v.end();++p)
     cout<<*p<<’\n’;

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

 for(auto x:v)
     cout<<x<<’\n’;

意思是“輸出 v 中的所有成員 x”。auto表示“讓 x 具有初始化器的型別,在這裡,也就是 v 的元素型別”。

語言特徵和標準庫中的元件並不能很好地處理複雜性問題。所以,我開始制定一些指導準則,幫助大家更好地使用C++。我的這一做法也得到了其他人的支援,目前我們正在一起開發名為“C++核心準則”的專案,以及工具支援的相關問題。你可以到麻省理工開源專案下找到相關準則:https://github.com/isocpp/CppCoreGuidelines 。指導準則試圖幫助程式設計師識別那些次優、容易出錯的表達方式,最終編寫出可讀性強、易於維護、簡單高效、型別安全和資源安全的程式碼(http://www.stroustrup.com/resource-model.pdf)。這並不是狂妄的想法!

這並不僅僅是為程式設計專家設計的。無論是專家還是初學者,都應該瞭解運用工具支援檢測問題的觀點。工具支援的早期版本可以在Visual Studio,Clang tidy和其他地方找到。

事實上,我們制定的指導準則已經受到了很多中高階程式設計人員的歡迎,他們把指導準則當作閱讀材料,學習如何更加高效地使用C++11和C++14。每一個準則都有基本原理的支援,同時提供了正反面的程式碼示例。

Guidelines Support Library的未來發展規劃是怎樣的?未來是否會像標準模板庫一樣,由主要的編譯器支援或是提供?

核心準則的目的是提供一種進一步利用C++的方式,可以用來回答“未來5年程式碼是什麼樣”的問題。利用C++11和C++14已經能夠很好地編寫程式碼了,但程式設計師個體忙於業務,沒有時間來評價新的工具,所以指導準則和工具支援就顯得很有必要。

具體的支援有兩種形式:

  • 彌補ISO 標準庫不足的GSL
  • 幫助執行準則、提供準確性保證的靜態分析工具

GSL很小(也就十幾種型別和函式),主要目的在於避免程式設計師直接使用C++當中最棘手、最不安全的部分。比如,C++裡有一種not_null 型別確保指標不是nullptr,和span型別把(pointer, size) pair傳遞給函式。

關於GSL在GCC、Clang和微軟的實現,可以參見GitHub上麻省理工學院下的開源專案:https://github.com/Microsoft/GSL 。考慮到相容問題,我們正在努力實現GSL的標準樣式規範。

核心指導準則是ISO標準委員會部分成員和其他外部人士共同執行的專案,它不是標準委員的工作。雖然我們已經向標準大會申請並希望某些GSL進入標準庫,但現在它們還是各自獨立的。

相比較其他程式設計師,高技能的程式設計師具有哪些品質?接觸程式設計學習較早,更加刻苦努力……

保持好奇心,願意終身學習下去;面對困難時,堅持不妥協;不止在程式設計方面,設計和電腦方面的基礎知識也必須堅實;樂於和系統使用者進行有效的溝通。

程式設計學習沒有“最佳年齡”或“最晚年齡”之說。如果你沒有在10歲、20歲或是30歲的時候開始接觸程式設計,這並不會影響你成為偉大程式設計大師的可能。我20歲才開始程式設計!重要的不僅是成為一名程式設計人員,你還要對自己設計的程式有感覺,你要了解相關學科、領域的知識經驗。我認識的一些優秀程式設計人員並不是電腦科學專業出身的:有學習數學的、工程的、歷史的、化學的、生物的,甚至還有哲學的。我認為,真正重要的是,趁你還很年輕的時候,能夠喜歡上某些學科,選擇具有挑戰性和感興趣的工作並養成良好的習慣。

我並不認為一味地刻苦努力,所有成績拿A就是正確的方法。許多優秀的程式設計師是非常全面的人才,遺憾的是,並不是所有的。

面對那些堅持“我不想知道如何彈鋼琴,只想知道如何像霍洛維茨一樣演奏”,急於尋求成功祕方的人,您的建議是?

霍洛維茨一生都在練習鋼琴演奏;如果你也想成為程式設計界的霍洛維茨,就要決心用一輩子的時間去練習和學習。記住,“臺上一分鐘,臺下十年功。”霍洛維茨第一次公開演出之前,花了很多年的時間來練習。應該有15年的時間都在練習。

要成為一名優秀的程式設計人員,你不需要是世界級的天才,也不用15年的程式設計學習,就可以開發出實際的應用程式。但我建議你,在把自己的程式設計成果展示給別人看之前,需要花時間認真地學習和練習程式設計。

我確信,霍洛維茨是從手指練習開始的,選擇專門為初學者編寫或是簡單的曲目練習。他沒有一開始就選擇李斯特的《匈牙利狂想曲》,沒有人上來就選擇最難的曲目。我猜想,很少有人在缺乏堅實基礎知識的情況下能達到高水平的成就。可以奔跑,但要在學會中低水平的技能(走路)之後。


——更多訪談


更多精彩,加入圖靈訪談微信!

相關文章