天天看點

Slashdot對Bjarne Stroustrup的采訪

注:前段時間Myan在CSDN上貼了一個對各大語言,以及OO和子產品化的評價的文章。下面這篇對C++之父的采訪中,Bjarne Stroustrup談到了自己的看法。通過大師們思維的碰撞,我們能從中學到什麼呢?

Slashdot對Bjarne Stroustrup的采訪

榮耀 馬皓明  譯

1.OO的發展已呈頹勢了嗎?(由Rambone提問)

20多年過去了,顯而易見,面向對象程式設計并非包治百病的“萬能藥”,請問您對OO範型(OO paradigm)的未來有什麼看法?您認為有什麼别的範型對它形成了挑戰? 

Bjarne:哦,20多年以前我就明白OOP的确不是“萬能藥”,這也正是C++支援好幾種設計和程式設計風格的原因。 

假如你喜歡羅裡羅嗦的詞語,你可以說C++是一門“multi-paradigm language”,不過,簡單地說C++是一門“OOPL”的确是不準确的。為此,我撰寫了一篇論文《為什麼C++不僅僅是一門面向對象的程式設計語言》(可以到我的“論文頁面”下載下傳)。我在OOPSLA(Object Oriented Programming Systems,Languages, and Applications)會議上展示了它,并且它幸運地得以存留。 

在《The C++ Programming Language》第一版中,我并沒有使用“面向對象程式設計”這個術語,因為我不想對那些大肆的宣傳推波助瀾。OOP的問題之一正在于肆無忌憚的人們宣揚它是一貼“萬能藥”。什麼東西一吹過頭了,最終必然會導緻對它的失望。

換句話說,雖然OOD和OOP是我鐘愛的設計和程式設計方式,但它們并非對所有程式所有細節來說都是恰當的風格。一些優秀的抽象例子并沒有使用類層次結構,同樣也被極佳地表達了出來。試圖将類層次結構尤其是單根層次結構應用到所有問題之上,很可能會導緻的确畸形的程式。

我大量使用了簡單的資料抽象(沒有繼承關系的類)和泛型程式設計(模闆和參數化類型的算法)。然而,我并不将此舉視作“泛型挑戰OOP”,它們是互補的技術。“尋找适合目标問題的設計方案”以及“在代碼中使用最佳語言結構來表達設計方案”,永遠才是關鍵。

組合運用各種風格可以寫出非常優雅的代碼。例如:

void draw_all(list< Shape*>& lst)

{

    for_each(lst.begin(),lst.end(),mem_fun(&Shape::draw));

}

在這段代碼裡,list和for_each()是C++标準庫中泛型設施的例子,它們被用來調用一個傳統類層次結構中的一個基類的一個虛函數。 

你甚至可以編寫一個适用于“任何具有Shape*元素”的标準容器的draw_all()版本:

template< class Container>

void draw_all(Container& c)

    for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

我選擇這個例子來喚醒那些依舊“生活在黑暗時代”并且認為C++不過是對C做了一些無趣的擴充的人們。

當然,這個版本的draw_all()所産生的代碼與清單周遊代碼(list traversal code)具有同等的效率,用于調用Shape::draw()的多态機制可以歸結為“通過一個函數指針數組來調用每一個函數”,此一機制已經應用于裝置驅動程式的調用之中。

每一年都會有新“範型”被吹捧出來,但它們多數都不具有基礎性的意義,而且大都缺乏新意。最有意思的可能是那些基于“元件”的模型,例如COM和CORBA。但我不能确定這些東西能否構成一種“新範型”,抑或它們不過是一套新的系統級的“構模組化塊”而已 — 我們難道說Unix程序(processes)是一種新範型嗎?總之,我對此類元件的基本觀點是:它們仍然不能和程式設計語言(如C++)以及形形×××的“傳統”的程式設計範型(如OOP和泛型程式設計)完美地內建。

2.使用C++進行系統程式設計(systems programming)(由ajs提問)

長期以來,C一直是UNIX世界系統程式設計語言,而且仍将如此。我知道您不願意對語言進行比較,是以我隻問問您,是否有什麼本質的原因,使得C++無法成為一門優秀的系統程式設計語言,或者說,不能引起C系統程式員的興趣?

Bjarne:讓“老家夥”學習新技術是件難事兒。 

Unix是25年前編寫的(我第一次用它是在1973年)。它所有的接口都定義為C函數調用、數組以及結構(structs)。到C++可用的時候,C語言幾乎唯我獨尊的傳統已經持續了10多年了。

Unix程式員沒有什麼合理的借口避免使用C++,相反,倒是有一些很好的理由去使用它。然而,有數不清的“不使用C++”的借口被提了出來,讓我來列出幾條:

C++速度慢

C++産生“臃腫的”代碼

 缺乏優秀的C++編譯器

 C++太複雜

C++沒有為系統程式員提供很多便利條件

C++的進階特性不适于系統程式設計

C++代碼不可移植

C++沒有ABI(application binary interfaces,應用二進制接口)

這些借口要麼荒謬至極,要麼很大程度上無關緊要。其中一些原因對于某些系統(比如缺少合适的C++工具的微型嵌入式系統)來說有一點道理,但對于Unix/Linux絕非如此,我将簡要地解釋一下原因。當然了,完整的讨論需要幾百頁紙頭,因為單單舉反例是不可能的,換句話說,想證明“對某人來講沒有什麼問題不可以解決”是不可能的。 

對于同樣的程式,C++可以産生與C同樣優秀的代碼,盡管去試吧!

C++就是被設計用來做那些事情的,目前傳遞的編譯器都遵守這個承諾。

當然了,使用任何語言你都能寫出糟糕的程式。C++雖然是一個強大的工具,但對它的誤用也會産生明顯畸形而臃腫的代碼。這可以和那些拙劣的程式員使用C語言編寫的“傳統的意大利面條”相“媲美”。要注意,一名優秀的C程式員并不會自動成為一名優秀的C++程式員。許多問題都是這麼冒出來的:某些優秀的C程式員總是想當然地以為自己随便學點C++語言特性,一周之内搖身一變就成了C++高手。

在一周之内C程式員的确可以從C++中獲益,但這僅局限于C++功能的一個子集和庫。 

C++支援許多威力強大的技術,而這些技術在C中頂多隻有微弱的支援,它們學起來是需要時間的。C程式員或許還清楚地記得成為“大師級”C程式員花了他們多少時間。為什麼成為一名“大師級”的C++程式員花費的時間就要短一些?我想不出任何理由。 

目前一代的C++編譯器在符合标準方面遠勝于幾年前的編譯器,但優化器(optimizers)卻與C共享。這可能會導緻問題,因為它排除了某些不适合C語言的有意義的優化,不過,至少與C共享編譯器的大部分,卻也能使那些懷疑C++是否可以産生與C等價的代碼的人們打消疑慮。 

我将在對問題9的回答中探讨語言複雜性的根源。在此,我想指出的是,C++的許多功能可以直接幫助人們編寫出高效、可靠且可維護的代碼,假如你不去用這些功能,你很可能最終需要利用低階語言結構模拟實作它們。

甚至那些“新的”或者“進階的”C++特性,例如模闆、異常以及運作時類型資訊(RTTI),也遵從“零開銷”設計原則。假如你需要這些特性,在一個現代的編譯器中使用它們比使用C語言來仿造這些功能更加高效(在運作時間和記憶體空間兩方面皆然)。我還沒有聽說任何C++語言功能在某些系統或嵌入式應用中“沒有用”或“負擔不起”。 

當然了,如果你不需要某種特性(RTTI往往用不着),或某種特性在特定情境下不适用(我可以想出一些不适用異常(exceptions)的程式),你不用它們就是了。“零開銷”原則允許你做出這樣的決策。這和“在一個給定的應用中不使用某種不适當的C語言特性(例如在一些嵌入式系統中禁止使用malloc())”沒什麼兩樣。

C++具有和C一樣的移植性。兩種情況下,你都需要對“系統依賴性”進行封裝以便移植。大型程式可以在Unix平台之間而移植,某些程式還可以跨越其他平台進行移植。這些技術廣為人知,而且當它們應用情況良好時,C++在“将無痛移植的系統概念進行正式化”的方面,甚至具有一個優勢。要想看一個例子,你可以參考“定義ACE平台”的C++庫(我的C++網頁上有連結)。

C++缺乏二進制接口(ABI),最難解決的技術問題可能非此莫屬了。C也沒有ABI,但在絕大多數(或者全部?)Unix平台上,都有一個“占支配地位”的編譯器,其他編譯器要想“有用”,都必須遵守它的調用約定和結構布局規則。在C++中,則有更多的“變數”,例如虛函數表(virtual function table)的布局,是以沒有任何廠商被準許為C++建立“所有競争廠商都必須遵守”的ABI。同樣道理,以前連接配接(link)由兩台不同PC上的C編譯器所産生的代碼也是不可能的,是以通常也不可能連接配接兩個不同Unix C++編譯器産生的代碼 — 除非有相容性開關。 

目前的解決方案通常是将“使用單一的編譯器”與“提供C級别的接口”二者結合使用,這并不理想,請參考我對問題10的回答。 

也就是說,我認為最主要的問題在于“教育”。對于“C++是什麼”以及“它能用來做什麼”,很多人的看法嚴重錯誤。錯誤的看法往往累積成學習的嚴重障礙。現在的C++與1985年的第一版相比已經大相徑庭,C++的ISO标準于1998年被準許。對我來說,目前的編譯器已足夠接近标準。我可以将那些利用較新功能的程式,在編譯器之間進行移植,以便在多種平台上進行性能測試,這其中标準庫功不可沒。

3.您會有什麼不同的設計?(由spiralx提問)

假如時光可以倒流到您設計C++之初,您會做出哪些改變?為什麼? 

Bjarne:我們永遠不能回到從前。不過,我想我最明顯的失誤有:沒有在引入多重繼承(multiple inheritance)之前引入模闆;沒能在C++編譯器第一版中搭配一個規模較大的庫。這兩個失誤在一定程度上是相關的。 

我沒能傳遞一個庫的原因在于我不知道如何編寫一個足夠好的庫。要想使容器高效而且類型安全,我們需要用到模闆,而直到1988或1989年我才得到一個支援模闆的編譯器。但隻有模闆還是不夠的,我們還需要一個針對容器和容器用法的“可以帶來安全性”的設計。直到Alex Stepanov帶來了STL,我們才有了這樣的一個架構。如果你想領教這些有趣的思想和睿智的觀點的“激情展現”,可以閱讀這篇對Alex的采訪。STL是C++标準庫的核心,它提供了标準容器和算法,還提供了它們的用法以及“以使用者自定義容器和算法進行擴充”的架構。自然而然,《The C++ Programming Language》對此進行了詳盡地讨論。

先于模闆引入MI所導緻的一個後果是慫恿了對類層次結構的進一步濫用。對于某些使用繼承機制的較為畸形的應用來說,模闆提供了一個簡單而高效的替代方案。 

讓我說說即使時光倒流我也不會修改的東西 — 相容性。即便C語言不是最佳相容人選,我也會選擇與某種其他語言相容。“創新”應該重點着眼于“改進”,那些行得通的東西應該盡可能保持不變。這樣的話,人們便可以繼續使用現有的工具和技術,也可以在功能業已完備的基礎上進行開發,還可以避免“重新發明輪子”,也不用重複教授那些與老内容雷同的“新”内容。是以,C++盡可能地接近于C — 但不能更接近了。 

這個問題的另一面是:你必須處理那些老錯誤以及相容性的問題。例如,我認為C的聲明文法就是一種失敗,不過,在C++中我還是采納了它。當是時,我考慮的變通方案以及改進方法并不能使情況好轉。不過我認為這是一個次要問題,更嚴重的問題是,随着C的發展演化,C++如何保持與之“接近”。

還有,即便反思過去,我也不會從标準C++中删除任何大的語言特性。添加新的大的語言特性也必須慎重。人們認為需要改變語言才能實作的功能,往往利用庫就可以實作。 

我認為C++标準委員會做了很了不起的工作。對一項規範達成共識并非易事。商業競争的公司之間要達成一緻意見,還要讓标準傳統沖突的國家感到滿意。然而,我認為所有時間和努力都沒有白費。标準C++比以前任何版本都更加接近我的理想,并且它還伴有一個很棒的标準庫。這些時間和努力是不可或缺的 — 除非你想被“事實上的标準”和“專有語言”所支配。我的第三版《TC++PL》講述了ISO标準C++,目前的C++編譯器接近這個标準。

4.為什麼沒有templated typedefs?(由Venomous Louse提問)

哦,真不可思議,這正是今上午我想找來讨論的家夥! 

typedef templates(或是template typedefs?天啊!)有時候的确能帶來便利,如下所示:

template< class T > typedef bool (* func)( const T &r );

……但這似乎并不合法,我不能回想起曾經在《The Design and Evolution of C++》中看到關于此類問題的任何東西。那該怎麼辦呢? 

Bjarne:我,還有标準委員會,低估了templated typedefs的重要性,我猜想将來會添上它。

實際上,D&E (第357頁)已經讨論了templated typedefs,并提出了一項常用的替代技術: 

這種擴充在技術上微不足道,但我并不能确定引入另外一種重命名特性有多麼明智。 

在定義一種新類型時,也可以使用派生機制對模闆參數進行部分特化(partial specification):

template< class U, class V> class X { /* ... */ };

template< class U> class XX : public X< U,int> { }; 

我猜想,在這種情況下,“我不願添加無明确實際應用需求的新特性”導緻了問題,但我卻更常因相反的錯誤(過于積極地添加特性)而受到指責。可是,在人們學會已有特性的用法之後,基于新經驗的新需求又開始湧現。 

順便說一句,你的例子 — 接受參數T并傳回一個bool值的函數,往往可以通過一個函數對象(function object)來表現。而函數對象天生就是模闆:

template< class T >

struct Predicate {

    bool operator()(const T &r) { /* ... */ };

};

如此一來,你就不需要typedef了,隻要這樣寫就可以了:

Predicate< Mytype>* p;

函數對象在内聯(inline)方面也要優于函數指針,是以使用它們可以編寫幹淨而快速的代碼。

通常來說,我推薦那些有着“在C++中它為什麼是或不是這樣”的疑問的人們閱讀《The Design and Evolution of C++》一書。 

5.問題……(由MrHat提問)

我(可能還有我們大多數人)了解您隻是通過您對C++語言的建立工作,以及您對編寫C++語言ANSI标準的協助工作。 

除了這項工程以外(雖然非常巨大),您日常還做了哪些工作?目前您在進行什麼項目?您還有更多的正在進行中的語言定義或标準嗎? 

Bjarne:C++相關工作是我日常工作的一大組成部分。雖然C++标準目前處于“維護模式”,但仍然需要投入一些注意力。我做了一些跟庫和程式設計技術相關的工作(今年晚些時候,我将會在《The C++ Report》上發表一篇關于“包裝器(wrappers)”的論文)。C++的教育以及“被誤導的程式員往往嚴重誤用C++”也讓我擔心。請參閱我寫的《把标準C++當作一門新語言來學習》一文(可從我的論文頁面下載下傳)。如你所見,我還進行一些寫作、演講并接受采訪。 

在标準委員會裡,我将大部分時間花在性能工作組(performance working group)中。成立這個組的目的是為了調查效率低下的根源,并且研究“拯救”劣質代碼的實作和程式設計技術。這項工作的需求場合之一是嵌入式系統。目前的語言使“近于最優化的解決方案”成為可能,但并非所有編譯器都能達到那種程度的效率,而且并非每個程式員都通曉編寫緊湊而高效的代碼的基本技術。 

一如既往,在網上仍然有很多關于語言擴充和新庫的讨論,但C++社群仍然沒有學會如何充分利用新的便利設施。我想我們可以從優秀的庫中獲得很多好處。标準庫和STL分别從一般角度和特殊角度展示了一些可以做的事情。最終,某些新庫将被标準化。 

我正努力提高對分布式計算的認知水準。為此,我閱讀了大量的東西,并試驗了一些小玩意兒。我關注的内容包括遷移性、可靠性、容錯能力以及安全性。這些研究領域也在“導緻我設計C++”的研究領域之列,是以從某種意義上說,我回到了“系統(systems)”老家。作為AT&T實驗室的一名管理者也要花費一定的時間,但可能不象你想象的那麼多,而且它看起來也不像是真正的工作 — 在那個職位,我既不寫代碼也不寫技術文獻。 

6.多重繼承(由MosesJones提問)

有三個互相關聯的問題:

 您認為多重繼承是一門真正的OO語言所必需的嗎?

在使用多重繼承設計系統時,您認為需要避免哪些陷阱(尤其在可維護性方面)?

 您認為有哪些措施,可以簡化多重繼承的可讀性,以減少新手可能做的危險動作?

Bjarne:假如你依賴于靜态(編譯期)類型檢查,你将需要MI。如果沒有MI的話,許多程式将被“扭曲”,并且你将不得不過于頻繁地使用顯式轉型操作(explicit type conversion (casting))。我不會聲稱自己知道什麼是“一門真正的OO語言” (即使果真有這麼一門語言),但是,假如你本質上必須始終使用顯式類型查詢(explicit type inquiry)的話,你使用的就不是一門真正的OO語言。 

我并不認為MI是一個特别嚴重的陷阱來源。MI與單繼承以及任何其他強大的語言特性所共有的明顯的問題,就是被濫用。我傾向于在簡單的聚合(aggregation)中以及實作多個接口時使用MI,例如:

class network_file_error : public network_error, public file_error {

    // ...

還有

class interface { // abstract class

    // pure virtual functions

}; 

class base_implementation { // useful functionality

    // data, functions, virtual functions

class my_implementation : public interface, protected base_implementation {

    // data, functions

    // override some base_implementation functions

    // override all interface functions

在後一個例子中,你可以讓使用者僅僅通過指向接口的指針或引用來通路my_implementation。這使得使用者代碼可以不依賴于“實作類(implementation class)”,而且系統不會遭遇所謂的“脆弱的基類”這個問題。“實作類”可被更換為一個改進版,而無需重新編譯使用者代碼(除了my_implementation自身)。 

遵循這兩種風格可以避免絕大多數問題。當然,你可以在《The C++ Programming Language》第三版和特别版中找到更加廣泛的讨論。(如需了解詳細情況、樣章或評論等,請通路我的個人首頁。) 

實際上,關于MI的問題通常意味着提問者已迷失于技術細節之中。對于幾乎所有程式來說,使用抽象類、模闆、異常以及标準庫的所帶來益處遠遠超過其招緻的問題。在一門帶有繼承的靜态類型的語言中,MI必不可少,但它并不屬于那些應該頻繁使用的特性。 

定義具體類(concrete classes)以表示簡單的類型,定義抽象類(abstract classes)以表示主要的接口,如果你将重點集中于此,可能會産生一個良好的設計。是以說,完成某個程式可能需要也可能不需要用到MI,但當真正需要的時候,它顯然是個優美的解決方案。

7.問題(由Edward Kmett提問)

“constrained templates”有希望被引入C++嗎?對程式員來說,目前使用模闆是件磨煉毅力的事。我聽說在模闆引入之初,“constrained genericity”就在标準委員會出現了,有沒有什麼重新思考那個決定的想法呢?

另一個問題在于“從Eiffel社群獲得了大量動力”的“Design by Contract”,我很希望看到它逐漸被納入C++标準,但我懷疑能不能心想事成。 

最後,Bjarne先生,您說過“當可以在C++中使用引用計數(reference counting)時(而不是“假如可以......”),它将是一種可選的技術”,有一本關于面向對象程式設計語言的書曾引用了這句話(但此刻我在Amazon上貼出ISBN也沒能找到這本書)。在引用計數對象技術的前沿有沒有取得重大進展呢?自從您的話被引用後,您的觀點是否發生了什麼變化? 

Bjarne:事實上,我當時說的大意是“當自動垃圾回收機制(auotmatic garbage collection)成為C++的一部分時(而不是“假如自動垃圾回收機制......”),它将是一種可選的技術”。

對于使用頻率不太高的資源來說,“引用計數”可能會非常有用,但我并不提倡将它作為一項“跟蹤記憶體”的通用機制。C++擁有良好的記憶體管理機制(比如構造器、析構器以及标準庫容器),但如果你需要更加“自動”的東西,插入(plugging in)一個可用的垃圾回收器(garbage collectors)是個恰當的選擇。(可參見《The C++ Programming Language》C.9.1節、我的C++網頁或者我的FAQ。) 

順便說幾句,精裝特别版《TC++PL》的ISBN是0-201-700735,而平裝第三版的ISBN是0-201-889544。 

對模闆進行限制且不使其能力受損,這并不像聽起來那麼簡單。詳細讨論請參閱D&E。問題之一在于:如果你根據基類來表達模闆參數限制的話,将會使你的系統設計向“每個屬性(property)都作為一個基類”的畸形風格發展。這極易導緻濫用多重繼承和間接表達“本來該直接表達”的東西。例如,說“一個類必須擁有一個<<操作符”比“必須繼承自‘Printable’(才能擁有一個<<操作符)”更加清晰。表達更直接的代碼,也就更加簡短,而且生成的代碼比“添加一個基類來表達一種限制”的版本品質更佳。 

特化(Specialization)和部分特化(partial specialization)機制為人們提供了許多希望從constraints中獲得的表達能力。例如,假如我有一個通用的排序函數模闆

template< class Container> void mysort(Container& c);

我還想為vector提供特别的排序算法,那這麼寫就可以了:

template< class T> void mysort(vector< T>& v);

我更歡迎帶有類型錯誤機制的模闆(templates with type errors)所提供的錯誤資訊。其中一些可以通過更好的編譯器技術而實作(人們正在為此努力),有些資訊則需要某種模闆參數限制或檢查才能做到,但我不知道如何做到這一點。幸運的是,程式員可以幫忙。考慮我在回答問題1時給出的例子:

如果有一個類型錯誤,将會在那個相當複雜的for_each()調用決議(resolution)時發生。例如,如果容器内的元素類型為int,那麼我們将會得到某種與調用for_each()相關的含糊的錯誤資訊(因為我們不能對int調用Shape::draw())。 

我編寫的代碼其實如下:

    Shape* p = c.front(); // accept only Shape*s

當無用的變量“p”在初始化時,目前絕大多數編譯器都将會引發一個容易了解的錯誤資訊。類似這樣的技巧在所有的語言中都很常見,并且已經開發用于所有新穎的構造(constructs)。在“産品代碼”中,我可能會這樣寫:

    typedef typename Container::value_type T;

    assert_equiv< T,Shape*>(); // accept only Shape*s

我使用了一個斷言以使它更加清晰。我把assert_equiv()的定義作為一個練習留給讀者J 

這引入了對問題的第二部分 — “design by Contract”的使用。既然“design by Contract”是一種設計風格,那麼就你可以在C++中使用它。為此,你需要系統地使用斷言。我個人傾向于依靠一些“特化的斷言模闆(specialized assert templates)”(請參閱《TC++PL》第三版)。我也認為這樣的一些模闆是“作為庫的一部分而标準化”的優秀候選者。 

然而,我并不認為語言将會直接支援類似于preconditions和postconditions這樣的東西,我也并不認為使用與程式自身本質上相同的語言而編寫的preconditions和postconditions是對asserts()的顯著改進。還有,我懷疑對類層次結構進行檢查(語言支援的conditions已提供)是否值得。通過在目前标準C++中更加系統地使用斷言,人們今天可以獲得非常顯著的好處。

8)一個聰明的問題(哈哈!)(由jd提問)

以軟體工程的角度來看,C++是一門基于對象的語言,而不是一門純粹的面向對象的語言。然而,它仍然以對象概念為中心,而并非以過程(procedures)為中心。 

然而,所有現有的處理器都是過程式的(procedural),并不存在真正意義上的OO CPU。 

您認為OO語言,例如C++,會在硬體級别上導緻OO系統的産生嗎?或者總是将OO思想限定在抽象層次上,而通過極其複雜的編譯器來實作“OO範型”和“過程化範型”之間的翻譯,這樣是否簡單一些? 

Bjarne:在設計C++之前,我的工作内容之一是直接支援“高階”功能的架構。我得出了一個結論:随着硬體迅速降低價格,速度加快,基于軟體的方案要優于高階硬體。這些優越之處遍及費用、彈性、潛在的使用者數量以及“軟體可以運作于其上”的計算機的壽命方面(設計并建構一台高階機器比一個傳統機器花費時間要長)。 

在可以預見的将來,“通過編譯器将高階結構映射為低階硬體基元”的方式仍将占據支配地位。

9.C++的複雜性 VS. OOP的簡單性(由hanwen提問)  

首先對本問題中那些可能的“煽風點火”的内容表示歉意J

您怎麼解釋目前C++的複雜性與面向對象程式設計大力鼓吹的簡單性之間的關系?以下是對本問題的更詳細的說明: 

近幾年來,C++的實作迅速膨脹,假如我沒記錯的話,您的著作《TC++PL》内容增加了一倍還多。C++已成為一門規模巨大、極其複雜的語言,我懷疑是否有任何個體(除了您之外)能将所有定義熟稔于心,更不用說團隊裡的程式員了。 

這意味着在任何項目裡要想全面使用C++是不現實的,而且肯定會為一個項目制定嚴格的“指導方針”來限制哪些特性可以使用,哪些不可以使用。從這個角度來看,“C++使得軟體開發更易管理”是值得懷疑的。是以我認為,作為一種意圖更高效地編寫更好的軟體的工具,C++是一個失敗者。 

C++的演化被幾條“格言”(你不必為你不使用的東西付出代價,與C相容,等等)所推動,從這個角度來看,C++是成功的,您是否認為這些“格言”需要重新斟酌呢?

Bjarne:煽風點火?我倒認為你非常有禮貌而且很專業 — 你的“編輯器或緩和劑(editor/moderator)”去除了真正過激的成分J 

某種程度的複雜性是不可避免的,我認為“在語言中以直接支援的形式實作強大而常用的技術”是一個優秀的思想(否則我不會這麼做J)。你見到過模仿實作“類層次結構”、“參數化的類型”或者“異常”的C代碼嗎?這類代碼傾向于将指針、轉型(casts)和宏弄得一團糟。在C++中,此類代碼可以幹淨而簡單地表達出來。至關重要的是,這些構造(constructs)有着詳細說明的語義(semantics),而不隻是一些對代碼的意圖進行說明的注釋。這樣一來,複雜性就從代碼轉移到了語言定義本身(以及編譯器)。

你認為在每個項目中沒有必要明确地使用C++的所有特性,這是正确的。然而,這并不意味着你需要強加一些限制性的“指導方針”。請問你什麼時候在一個項目中明确使用了Unix或NT的所有功能?你希望某個管理者确切地告訴你作業系統的哪些功能可以使用哪些不應該使用嗎?它們本來就跟具體的項目毫無關系。

典型的“指導方針”顯然來自于“黑暗時代”,它們基于“幾年前世界的狀态”以及對“什麼複雜、什麼不複雜”的生疏的假定。那些制定這些“指導方針”的人肯定會說:從總體上看,教育機構在“使學生重點學習C++中有效的關鍵程式設計技術”方面做得很差。其後果是導緻了“混亂的C風格代碼”與“過分臃腫的Smalltalk風格的類層次結構”的攙雜狀況。對轉型(casts)和宏的大量使用,是非最優化使用C++的普遍共同點。

我見過許多成功的C++項目(遠比失敗的多)以及對C++的“良好”運用。“良好”一詞的意思是:優雅、高效、可靠和可維護。是以,對很多人來講,C++的确已達到了預期的設計效果。請記住,我隻對C++做過一點點明确的、“記錄資料良好”(well-documented)的承諾(請參閱《D&E》 和《The C++ Programming Language》)。我不是商業OO騙局的效力者。 

我想我明白“C++的成功使用”與“尊重C++的局限性(施加于‘設計’之上深思熟慮的限制)”以及“積極地使設計思路适應于已提供功能”之間的互相關系。例如,如果你在建構一個層次深的層次結構時,拒絕使用抽象類,并且在每一層都定義很多資料成員,你真的不應該驚訝于編譯時間之長、重新編譯之頻繁以及需要了解“某物在何處定義”的問題之多。類似地,如果你不使用C++的功能,而使用C風格字元串、數組、普通結構(plain structs)以及大量的指向低階資料結構的指針而搞亂你的代碼,你也真的不應該驚訝于碰到C風格的問題而不是獲得C++所承諾的好處。

C++語言主要的輔導内容在《TC++PL》第一版中有186頁,第二版有282頁,第三版則有360頁。“增加的部分”目的在于進一步強調程式設計技術。書頁數增加(從第一版327頁到最新特别版的1040頁)的其它原因是為了描述更多的程式設計和設計技術以及标準庫方面的資訊。“特别版”花了363頁講述标準庫,這還不包括該書其他部分的标準庫應用示例程式。

10)提給Bjarne的問題(由scherrey提問)  

1989年,通過BIX online service,您和Greg Comeau把我帶進了C++的世界,當時你們兩位開始示範(并最終使我信服)OO并非一時流行的狂熱,而且C++語言可以高效地實作OO。《Computer Language》雜志那段時間正在開設“本月語言”特色專欄,當時程式設計語言有種“來也匆匆、去也匆匆”的趨勢。

在我的印象中,您強調以下兩個主要目标:a.建構一種語言,使之可以處理那些C語言難以對付的巨型項目;b.在“特性”和“效率”兩方面達到一種平衡,是以開發者要為他用不到的特性而有所付出。 

以我個人在種類極其繁多的項目(包括非常“受拘束”的嵌入式系統和大型跨平台的企業級系統)中的C++使用經驗來看,毫無疑問,第一個目标已經取得了重大進展,而第二個目标可能已經完全達到了。

然而,最讓我失望的是,在系統級的開發中缺少“用C++完全代替老舊平常的C”的能力,而這正是可以通過語言的抽象特性獲益最多的領域,同時這也是您聲明的語言目标之一。虛函數、繼承決議(inheritance resolution)之類的東西的實作技術非常具有“經驗主義”性質,是以,在早期我就曉得定義标準ABI(application binary interfaces,應用二進制接口)是不可能的。如今已經過了整整十年時間,一個驚人強大的标準也已經出台,這些實作方面的差異,比基于架構方面的考慮更加具有“人為性”。

如今,開放源代碼運動在商業和非商業方面的普及工作都進展得如火如荼。不幸的是,C++不能用來提供可連接配接的API(linkable API),除非降級到嚴格受限的基于C的連接配接,或迫使調用你的庫的所有使用者和你使用一樣的編譯器 — 因為不存在關于結構和調用約定的标準表示。

您是否認為現在應該優先考慮ABI的标準化問題了?果真如此,将使用什麼機制定義這些接口(事實上的标準 vs.正規的标準,等等)?誰來做這項工作,還有,哪些工作可以促進這項技術的發展?

Bjarne:Ben,你好。

我想我做到了高效、通用以及某種程度上的優雅,然而,我低估了連接配接器(linker)的問題,我可能也低估了因為與C相容而滋生的問題。

假如我是一個平台供應商,我在幾年之前就會優先考慮實作C++ ABI了,這麼一來,當編譯器高度符合标準時,所有基于我的平台的廠商就會提供和我一緻的實作。據我的了解,這樣的工作已經由SUN公司(針對SPARC系統)和一群針對Intel公司即将推出的Merced架構(IA-64,請通路http://reality.sgi.com/dehnert_engr/cxx/)的供應商而展開了。

我将會鼓勵所有人 — 尤其是那些編寫“意圖作為協作成果的一個組成部分”的軟體的人來推動實作這種平台ABI标準。假如你在此類人之列,請為在你鐘愛的平台上實作C++ ABI而遊說。不幸的是,對于如何做到這一點,我無法提供具體建議。 

盡管使用以類或模闆表述的高層抽象機制要好得多,但“在系統接口層與C相容”已經慫恿人們去使用C風格的字元串、數組和結構。他們不但沒有使低層工具局限于系統層和類的實作之内,反而讓低層結構及其指針彌漫于設計之中。顯而易見的後果就是類型錯誤、“狂野”指針(wild pointers)、數組越界錯誤和記憶體洩漏。大量的宏及轉型操作(casts)使得代碼更加晦澀。眼看着人們陷入一些本可避免的混亂之中,我感到悲哀。

問題的部分原因在于差勁的教育。良好的教育必然是部分解決方案,然而教育也隻能起這麼大的作用。在我們期望“新手”沖出C子集并使用更強大的技術之前,那些利用現代C++思想而實作的庫和工具必須業已遍地開花。

人們應該意識到C++的入門要比C的入門來得容易些。要學習的C++的“最佳初始子集”并不是“C的絕大部分”,與通常情況相比,C++的學習曲線要平滑得多。進一步的讨論請參閱《把标準C++當作一門新語言來學習》一文(可從我的論文網頁下載下傳)。

我們一直使用C++編寫的應用軟體和專門的庫來為程式員提供一個高階的工作環境。标準庫的重大意義之一就是為所有人提供了這樣的一個榜樣。使用标準便利設施(例如string、vector、map以及算法)來編寫代碼,确實可以改變人們的程式設計方式以及對程式設計的思考方式。我希望看到“用于定義和實作标準庫”的技術也應用于許多其它領域,進而産生同樣的利益 — 無論是在源代碼尺寸、類型安全、代碼清晰度還是運作時效率方面。 

我認為,試驗标準C++中的更進階、更有趣的部分的日子已經來臨。舉個例子,我新撰寫的一份補充材料Standard-Library Exception Handling展示了一種程式設計風格,它更加顯著地違反了“C的普遍常識”,但它可以使代碼更加簡潔。盡管此文并非為新手而寫,但從中可以一瞥标準庫的内部運作情況。

當然了,對于“産品代碼”我們必須更加謹慎,因為它們與老代碼的相容性的要求更高。然而,我們也不應囿于老舊風格或相容問題,以至于不敢嘗試更現代、更高效的風格。現在有了原生的C++風格( native C++ styles),我們應該使用它們。

一些附加評論

Coward的匿名人士的評論

這真是一些有趣的東西,我将轉發給這兒的每一個人。 

我隻提一個“負面”的問題:他真的需要那麼多次提到他的書嗎?好像他從來沒有錯過一次說“請參閱我的第三版《TC++PL》”之類的話的機會,我往往不信任那些多次提及他們自身或者他們的産品的人。

當然啦,這個問題微不足道,感謝這篇精彩的訪談。 

Bjarne:不妨設想對一位嚴肅的畫家或雕塑家進行一次廣播采訪。對于這樣的一位藝術家來講,真正重要的是他(或她)的作品,但又不可能通過廣播展示這些作品,你将預期獲得很多關于作品以及“人們可以去參觀這些作品”的博物館的參考資訊。描述性的言辭絕對是不夠的。拙劣的藝術家可能會将讨論主題從作品轉移到私生活或政治觀點上以作為彌補話題,但嚴肅的藝術家們不會這樣做。 

我雖然不是一個藝術家,但對于這樣的一個采訪來說,同樣存在類似的問題。我想展示代碼并對問題進行嚴肅的讨論,但“Q&A (問答)”的方式使我無法做到這一點。我的解決辦法就是引用我已經出版的著作和我的個人首頁。

畢竟,我已經出版的著作是首要的C++資源。是以,我可以坦然地說,如果你希望了解更多的資訊,請通路我的個人首頁,閱讀《TC++PL》、《D&E》或我的論文。 

C++和科學計算(由J. Chrysostom評論)

作為一名即将畢業的科學計算專業的學生,我有時非常疑惑,為什麼C++對數學和科學計算的支援如此有限,而FORTRAN,那個醜陋且難以控制的“野獸”,才是計算數學的唯一“避風港”。 

Bjarne:請看看關于C++數值計算庫的連結(例如Blitz++、POOMA、MTL和ROOT),你可能還會跟蹤到另外一些C++數值計算方面的網頁。 

Davorama的評論

這些問題是如此不尋常,使我無法做出定論,或許你們這些人可以提供一些想法?

您怎麼看待模闆元程式設計(template meta programming)?您是否認為它是一種“恩賜”,使得聰明的程式員可以做出像Blitz項目那樣酷斃了的東西?抑或是它所帶來的便利完全被其“晦澀乃至近乎看不懂”的實作代碼給抵消了? 

Bjarne:我确實喜歡一些使用C++進行數值計算方面的東西。常見的思路是模闆可被用于消除那些僞臨時對象(spurious temporaries)。結果往往是以它們自己的遊戲規則擊敗Fortran,同時還保持了規範的數學記号。在我的個人首頁上,你可以看到以下連結:來自LANL的POOMA、來自Warterloo U.的Blitz++、來自Notre dame的MTL。《TC++PL》在數值計算一章有關于這方面基本技術的解釋。 

我認為實作代碼的複雜難懂無關緊要。實際上,我認為那些代碼比另外一些代碼(比如說C核心代碼)要好懂多了。在效率頭等重要時,你不該過分抱怨優化技術有多麼難懂。畢竟如果你是一個真實使用者的話,你沒必要閱讀它們的實作代碼。

sethg的評論

在回答中似乎有一個普遍的主題,即“那些抱怨是基于過時的資訊,請使用遵從标準的新編譯器和STL。” 

有沒有人為我們這些C++新手維護一個圖表,展示目前哪些可用的C++編譯器違反了C++标準檔案的哪些小節? 

Bjarne:一個大緻的回答:目前大廠商提供的編譯器都已相當符合标準(我使用它們),例如Borland、GNU、 Metrowerks、 Microsoft以及SGI。 

更詳細的資訊請參考LANL的清單(在我的C++網頁上有對POOMA站點的連結),或者參考一個試圖跟蹤這類資訊的紐西蘭站點。一些廠商,例如Borland,在它們的網站上公布Plum Hall評估的結果。

還有,你說的對,我認為針對C++而報告的問題有很大比例可以歸結為“誤解”和“誤用”。“有一個現代的C++編譯器”是試驗我建議的一些技術的先決條件,但是請不要以為一個新編譯器本身就會帶來多大幫助。你需要真正地改變你的工作方式,不幸的是,現實世界中有太多因素使得這種改變難以實作(遺留代碼、缺少學習新技術的時間、多人協作以及過時的風格規則等等)。我沒說過這很容易,我隻說過這有可能,而且很多人已經取得了成功。 

hanwen的評論 

可能如今标準庫可以減輕我的一些苦惱,但我不想再學習這麼一個大型C++元件,另外還因為我知道即便如此,C++仍然不夠好。它究竟會不會支援反射(reflection)、高序函數(higher order functions)、垃圾回收?。 

這麼看來,我發現您的“C++的入門要比C的入門容易一些”的說法是危險的,您在《把标準C++當作一門新語言來學習》中所舉出的例子也是如此,因為它們暗示這兩門語言中的任意一門都能夠或者應該成為一門“入門”語言。 

一門語言,差別對待堆和棧上的非直接對象(non-immediate objects),沒有自動垃圾回收機制,允許使用指針,無初始化機制,沒有任何“高序(higher-order)”内容,您确實希望人們随着這樣一門語言成長嗎? 

您教育人們關于C++的内容:和錯誤的思想鬥争,告訴他們哪裡适用C++。但是,您未曾告訴過他們哪裡不适用C++。C++廣為流行,以至人們容易産生這樣的誤解:這種流行性說明,C++是一門優秀的程式設計入門語言,或者,C++是一門适用于編寫非常巧妙的程式的語言,等等。 

Bjarne:實際上,你說的沒錯。我認為讓人們學習一種“當他們畢業後就再也不可能使用”的語言是不妥當的。Lisp和函數型程式設計語言(functional language)社群并沒有使得自動垃圾回收機制和高序(higher-order)的方方面面都成為主流,盡管它們擁有學院派以及教育機構長達20多年的熱情支援。很明顯,我認為讓人們使用像C這樣的低階語言也非理想選擇。

鑒于目前程式設計及設計教學的糟糕情況,C++可能會取得一個大的進步。當然了,人們也可能無法從C++的抽象機制中獲益,而是退化到使用C或C++來編寫等價于彙編代碼的東西。通過STL,C++社群或許已經使更多的人了解函數型程式設計(functional programming)技術,或許C++社群已經将此類技術應用于現實問題之上,比以前所有語言所做的總和還要多。函數對象(function objects)不是最具彈性的閉包(closures),但這并不影響這樣的事實:人們了解、喜歡并且使用它們。 

《把标準C++當作一門新語言來學習》(在我的論文網頁上有連結)一文清晰地陳述了這些方式的作用範圍并探讨了原因。看看1988年的C++,它沒有模闆、異常、RTTI和标準庫,它的确是一門不同的語言 — 一門不支援絕大多數現代C++技術的語言。 

如果你需要垃圾回收機制,有很棒的免費的或者商業支援的C++垃圾回收器可供選用(請參考我的C++網頁上的連結)。C++垃圾回收器之是以如此高效,原因之一即是C++差別對待配置于棧上的對象和配置于自由存儲區的對象。 

姓Coward的匿名人士的評論

啊,正是這個家夥,他一度篡用“C”這個名稱來為他的新語言命名,還在AT&T到處說K&R的C語言是“old C”……直到Dennis Ritchie叫他住嘴為止。 

我想他不得不承認,委員會幫他擺平了這門語言早期版本中的大量的問題,而且我也認為他不會宣稱目前的C++是完美無暇的。 

鑒于他的成功和知名度,或許我自己有點狂妄自大了J 

Bjarne:我并不真的認為自己極其缺乏謙遜。 

要知道,我與Dennis(雖然不十分密切)還有Brian Kernighan(密切地)共事過十多年。我不認為我盜用過“C”這個名字,即使果真如此,也沒有人會向我索賠J 

繼續閱讀