在1994年,我主要關心的是如何使ISO C++標准盡可能地好--同時在它所包含的特性和規范的質量兩個方面--並獲得多數人的同意。即使人們不接受某種規范,也不會影響它(規范)的良好性。ISO標准沒有強制力,因此有些人認為自己不值得浪費時間來適應它,除非(群體)社團的壓力能夠使他們確信該規范的價值。對於一個實現者來說,適應環境是很重要的額外工作,因此適應環境是一個有意識的決定,並且需要分配一些資源,而這些資源本來可以在其它地方使用。某些晦澀的語言特性很難在某些編譯器中實現。我們可以實現或者購買類庫,而且領先的、可靠的實現者(implementer)也有機會用自己的富於想像力的專利特性來"鎖定"用戶。因此,我認為要點是:讓委員會成員和他們所代表的組織確信該標准的文檔是他們所期望看到的最好的文檔。
在做了很多工作之後,該委員會獲得了成功。1997年10月,在Morristown(New Jersey,USA)會議上,技術成員的最終投票結果是43-0。在獲知這個結果以後,我們進行了慶祝活動!在1998年,ISO成員國以空前的22-0的投票結果批准了這個標准。為了獲取大家的一致同意,委員會做了大量的技術工作,也使用了一些外交策略:在那個時候,我喜歡說"政治問題無法解決;我們必須找到引發該問題的技術問題並解決它"。我無法想象僅僅通過投票,因為少數服從多數才簡單"解決"的問題,同時,由於"政治上的討價還價"的問題也危害了我們最好的技術判斷--而這個問題(模板的分開編譯)仍然在"惡化",需要尋找一個更好的技術方案。
在最後投票之前的一年裡,委員會的工作是:
1. 細節、細節和更多的細節。
2. STL
3. 模板的分開編譯
第一個問題非常明顯:國際標准必須用大量的篇幅來關注細節信息;實際上,實現(implement)與已有標准的兼容性是標准的關鍵目標,同時還是實現之間的工具和應用程序能夠遷移的基礎。標准是一個712頁的文檔(加上索引等內容),它是采用高度技術化的和正式的方式編寫的,因此為了理解真正的含義需要很多的細節信息。像以前一樣,我在新語言規范上附加了新版的"C++編程語言",以提供更有幫助意義和面向用戶的語言描述。
STL的出現
第二個問題,STL("標准模板類庫",它是ISO C++標准類庫的容器和算法框架)成為標准的一部分是一個主要的創新,並且它成為了新的用以思考已經出現的編程技術的出發點。STL基本上革命性地脫離了我們原來思考容器和容器使用問題的方式。在Simula早期,容器(例如列表)曾經是困擾人的:如果,並且只有當某個對象已經(顯式或隱式地)衍生自那些包含編譯器所需鏈接信息的特定的"Link"或"Object"類的時候,它才能被放到容器中。這種容器基本上是引用Link的容器。這暗示著基本的類型(例如int和double)不能直接地放入容器中,數組類型(它直接支持基本的類型)必定跟其它的容器不同。此外,如果我們希望把真正的簡單類對象(例如complex和Point)放入容器中,那麼它們在時間和空間上就無法達到理想效果。它同時還暗示著這種容器不是靜態類型安全的。例如,Circle可以被加入列表中,但是當它被提取出來的時候,我們只知道它是一個Object,需要使用一個轉換(顯式類型轉換)來恢復其靜態類型。
Simula容器和數組關於內建和用戶定義類型(只有後來的一些可以放入容器)、關於容器和數組(只有數組能夠保存基本的類型;數組不能保存用戶定義類型,只能保存用戶定義類型的指針)都有一些奇怪的條款。Smalltalk使用了相同的方法,也有相同的問題,後來的一些語言(例如Java和C#)也是這樣的。由於它有明顯的效用,而且很多設計者現在都熟悉它,所以很多C++類庫也遵循這種模型。但是,我卻發現它是無規律的和低效率的(在時間和空間上),使用它開發真正通用的類庫是不可以接受的。這就是我在1985年沒有為C++提供適當的標准類庫(這個失誤)的根本原因。
當我編寫D&E的時候,我知道了一種容器和容器使用的新方法,它是由Alex Stepanov開發的。Alex當時在HP實驗室工作,之前在Bell實驗室工作了多年,在那兒他接近了Andrew Koenig,我也在那兒與他討論過類庫設計和模板機制。他鼓勵我進一步研究某些模板機制的泛化和效率,但是很幸運的是,他卻沒有說服我讓模板更類似Ada泛型。如果他成功了,他就無法設計和實現STL了!
在1993年末,Alex在泛型編程技術方面顯示了他最近十年的長期研究的進展,這種技術是基於嚴格的數學基礎的、目標是成為"最通用和最高效"的編程技術。他是一個容器和算法的框架。他首先聯系了Andrew,Andrew花幾天時間研究這種技術之後,就把它展示給我了。我的第一反映是很迷惑。我發現STL的容器和容器使用方式是多余的,甚至於是丑陋的。與很多通曉面向對象編程的程序員一樣,我認為自己知道容器的樣子與STL代碼的樣子有怎樣的不同。但是,在我建立工具列表(我認為這個列表對於容器來說是很重要的)的幾年裡,令我驚訝的是,我發現STL除了一個條件之外,符合其它所有的條件。那個缺少的條件是使用通用基類為所有的衍生類(例如所有的對象或容器)提供服務(例如永續性)。但是,我不認為這種服務對容器的概念有本質的意義。
它讓我花了幾周時間才感覺到STL比較"舒適"。在那以後,我擔心把一個全新樣式的類庫介紹給C++群體已經太晚了。讓標准委員會在標准進行過程中這麼遲的時候接受新的和革命性的東西的成功幾率是非常小的(的確是這樣的)。即使出現最好的情況,標准也會被延遲一年--而C++群體急切需要該標准。同時,委員會本質上是一個保守的團體,而STL是革命性的。
因此成功的機會是很渺茫的,但是我還是在它上面進行著辛勤的工作。畢竟,由於C++沒有足夠大的、足夠好的標准庫,我的感覺非常糟糕。Andrew Koenig盡了最大的努力來鼓勵我,並且Alex Stepanov用他知道的最好的東西來游說Andy和我。幸運的是,Alex不太了解在委員會中使某種東西成為主流的難度,因此他不太沮喪,並且繼續研究技術方面,繼續為Andrew和我講授。我開始給其他人解釋STL背後的想法。
1993年10月,在加利福尼亞的San Jose舉行的標准委員會會議上,我們邀請了Alex進行晚間演講。"它叫做C++編程的科學,它處理了規則類型的大多數原則--連接構造、賦值和等同。我還介紹了轉發迭代子的原則。我沒有提起任何容器的問題,只提到一個算法:查找"。這次演講是活躍的下層社會的大膽創新,而其巨大的樂趣使委員會的態度從"現在讓我們作些主要的事情"變成了"等等,讓我們瞧一瞧"。
這正是我們需要的"暫停時間"!在後來的四個月中,我們進行試驗、爭論、游說、講授、編程和重新設計,這樣Alex才能在1994年三月加利福尼亞的San Diego委員會會議上提供STL的完整說明。1994年末在HP一個會議上,Alex提出了C++類庫實現,我們在很多細節上達成了一致,但是STL的大小成為了主要的障礙。最後,在Alex的催促下,我拿起筆,逐字地刪除,大約刪掉了三分之二的內容。對於每一個工具,我都必須向Alex和其他類庫專家非常簡短地解釋為什麼它不能被刪掉、為什麼它使大多數C++程序員受益。這是個可怕的過程。Alex後來聲明說這讓他心痛。但是,這樣的刪減造就了現在知名的STL,並讓它1994年10月在加拿大Waterloo的會議上成為ISO C++標准的一部分--而原始的和完全的STL都沒有達到這個目標。對"簡化STL"的必要修改也把標准延遲了一年以上。回想起來,我認為當時我們做的事情造成的傷害比預料的要小一些。
在對采用STL的可能性的討論中,我記得一件事情:Beman Dawes冷靜地聲明自己認為STL對於普通程序員來說過於復雜了,但是作為試驗,他自己實現了10%,從此他不再認為STL超過了標准的難度。Beman是委員會中很少編寫應用程序的人。不幸的是,委員會趨向於由建立編譯器、類庫和工具的人員所控制。
在STL方面我信任Alex Stepanov。在STL之前,他花了十年以上的時間,用一些無用的語言(例如Scheme和Ada)來研究基本的想法和技術。但是,Alex第一個堅持要求其他人一起參與。David Musser和Alex在泛型編程方面一起工作了約二十年,Meng Lee與他一起在HP工作,幫助他編寫最初的STL。Alex和Andrew Koenig之間的電子郵件討論也有幫助作用。除了苛刻的試驗之外,我的貢獻很小。我建議與內存相關的所有信息都集中到一個對象中--就形成了分配器(allocator)。我還草擬了Alex想法的初始需求表,建立表格記錄STL算法和類對它們的模板參數的要求。這些需求表實際上表明這種語言的表達能力不足--這種需求應該成為代碼的一部分。
1.1 STL的理念
那麼什麼是STL呢?到目前為止,它作為標准C++的一部分已經快十年了,因此你的確應該知道它,但是如果你不熟悉現代的C++,那麼我就有必要對它的想法和語言使用方式作一些簡單的介紹。
我們來考慮一個問題:把對象存儲在容器中,並編寫算法來操作這些對象。我們按照直接、獨立和概念的混合表現方式來考慮這個問題。自然地,我們希望能夠在多種容器(例如列表、向量、映射)中存儲多種類型(例如整型、Point、Shape)的對象,並在容器中的對象上應用大量的算法(例如排序、檢索、積聚)。此外,我們希望使用的這些對象、容器和算法都是靜態類型安全的、盡可能地快速、盡可能地簡潔、不冗長、易於閱讀。同時實現這些目標並不容易。實際上,為了解決這個難題,我差不多花費了十年還是沒有找到成功的方法。
STL解決方案是以帶元素類型的參數化容器以及與容器完全分離的算法為基礎的。容器的每種類型都提供一種迭代子(iterator)類型,只使用這些迭代子就可以實現對容器中所有元素的訪問。通過這種方式,我們可以編寫算法來使用迭代子而不用知道提供迭代子的容器的信息。每種迭代子類型與其它類型都是完全獨立的,除非要為必要的操作(例如*和++)提供了相同語義(semantics)。我們可以用圖