天天看點

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

假如你在網上搜最好的c++源代碼。「毀滅戰士3 | doom 3」的源代碼肯定會被提到好多次,這篇文章就來證明為何如何說。

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明
doom3 bfg是用c++寫的,一種龐大的語言,它既能寫出優秀的代碼,但也讓人憎惡到眼睛流血。幸運的是,id software退而求其次,使用c++子集,接近于“帶類的c”,如以下幾條規則: 沒有異常 沒有引用(使用指針) 少用模闆 使用常量(const everywhere) 類 多态 繼承

很多c++專家不建議使用“帶類的c”這樣的方法。然而,doom3從2000開發至2004,沒有使用任何現代c++機制。

doom3由少量的幾個工程組成,這兒有它的工程清單和一些類型統計。

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

以及它們之間的依賴關系圖:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

doom3定義了很多全局函數。但是,大部分内容實作是在類中。

資料模型使用結構體定義。為了在源代碼中對結構體的使用有個更具體的了解,在下圖中将它們以藍色分塊顯示出來。

在圖表中,代碼被表示為樹形圖,樹形圖表示法能使用嵌套的矩形來表示樹狀結構。而樹結構用來表示代碼分層結構。

工程包含命名空間。

命名空間包含類型。

類型包含函數和域(field)。

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

我們可以觀察到它定義了許多的結構體,比如doomdll 40%的類型都是結構體。它們被有條理地用來定義資料模型。該實踐已經被很多工程所接受,這種方法有個最大的缺點是多線程應用,結構體的public變量并非不可改變的。

為何支援不可變對象,有個重要原因:能顯著地簡化并發程式設計。考慮下,寫個合格的多線程程式是個艱巨的任務嗎?因為很難同步線程通路資源(對象或者其他os資源)。為什麼同步這些操作很困難呢?因為很難保證在資源競争狀态下多線程對多個對象進行正确的讀寫操作。假如沒有寫操作呢?換句話說,線程隻通路這些對象,而不做任何變動?這樣就不再需要同步操作了!

讓我搜尋下隻有一個基類的類:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

幾乎40%的結構體和類都隻有一個基類。通常,oop(面對對象程式設計)使用繼承的好處之一是多态,下面藍色标明了源代碼中的虛函數:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

超過30%的函數是虛函數,少數是純虛函數,下面是所有虛基類清單:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

隻有52個類被定義為虛基類,其中35個類隻是純接口,也就是這些接口都是純虛函數。

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

我們來搜搜使用了rtti的函數

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

隻有非常少的函數使用了rtti。

為保證隻使用oop最基礎的概念,不使用進階設計模式,不過度使用接口和虛基類,限制了rtti的使用并且資料都定義為結構體。

至此這份代碼跟很多c++開發者所批評的“帶類的c”沒太大差別。

<a target="_blank"></a>

許多類是從idclass繼承下來的:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

idclass提供如下服務:

建立執行個體化

類型管理

事件管理

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

一般來說,字元串是一個項目裡用的最多的對象,許多地方需要使用它,并且需要函數來對其進行操作。

doom3定義了idstr類,幾乎包含了所有用的字元串操作函數,無需再自己定義函數來接受其它架構所提供的字元串類。

很多工程用了mfc後,它的代碼就會與mfc類型高度耦合,并且在代碼的任何一處都能發現mfc類型。

在doom3裡,代碼和mfc是高度解耦的,隻有gui類才會直接依賴它。下面的cqlinq查詢可以展示這點:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

這樣的選擇對生産力有很大的影響。事實上,隻有gui開發者才會關心mfc架構,其它開發者不應該被強制在mfc上浪費時間。

幾乎在所有項目中都會用到公共工具類,就如以下查詢的結果:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

正如我們所看到經常使用的就是公共工具類。假如c++開發者不使用一個良好的公共工具架構,那就會為解決技術層面問題花費大部分的開發時間。

idlib提供了很多有用的類用于字元串處理,容器和記憶體。有效促進了開發者的工作,并且能讓他們更多的關注在遊戲邏輯上。

doom3實作了非常難的編譯器,對于c++開發者而言,開發文法解析器和編譯器不是件輕松的事。盡管如此,doom3的實作非常容易被了解并且編寫得十分幹淨。

這兒有這些編譯器的類的依賴圖:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

這兒還有編譯器源代碼的代碼片段:

「毀滅戰士3」源碼就是“保持簡潔”的證明「毀滅戰士3」源碼就是“保持簡潔”的證明

我們也看過許多文法解析器和編譯器的代碼,但這是第一次我們發現編譯器是如此得容易了解,和整個doom3源代碼一樣。這太神奇了。當我們探究doom3源代碼時,我們忍不住會喊:喔,這太漂亮了!

即使doom3選擇了很基礎的設計,但它的設計者所做的決定都是為了開發者能更多的關注遊戲邏輯本身,并且為所有技術層面的東西提供便利。這提高了多大的生産力啊。

無論何時使用“帶類的c”,你應該明白你自己在幹什麼。你必須像doom3的開發專家一樣。但不推薦初學者忽視現代c++建議而冒險。

原文釋出時間:2015-01-24

本文來自雲栖合作夥伴“linux中國”

繼續閱讀