寧可編譯和鏈結時出錯,也不要執行時出錯

2021-06-18 09:26:23 字數 2534 閱讀 3494

除了極少數情況下會使c++丟擲異常(例如,記憶體耗盡 ---- 見技巧7)外,執行時錯誤的概念和c++沒什麼關係,就象在c中一樣。沒有下溢,上溢,除零檢查;沒有陣列越界檢查,等等。一旦程式通過了編譯和鏈結,你就得靠自己了 ---- 一切後果自負。這很象跳傘運動,一些人從中找到了刺激,另一些人則嚇得摔成了殘廢。這一思想背後的動機當然在於效率:沒有執行時檢查,程式會更小更快。

處理這類事情有另乙個不同的方法。一些語言如smalltalk和lisp通常在編譯鏈結期間只是檢查極少一些錯誤,但卻提供了強大的執行時系統來處理執行期間的錯誤。不象c++,這些語言幾乎都是解釋型的,在提供額外靈活性的同時,它們也帶來了效能上的損失。

不要忘了你是在用c++程式設計。即使發現smalltalk/lisp的方法很吸引人,也要忘掉它們。常說要堅持黨的路線,現在的情況下,它的含義就是要避免執行時錯誤。只要有可能,就要讓出錯檢查從執行時退回到鏈結時,或者,最理想的是,編譯時。

這種方法帶來的好處不僅僅在於程式的大小和速度,還有可靠性。如果程式通過了編譯和鏈結而沒有產生錯誤資訊,你就可以確信程式中沒有編譯器和鏈結器能檢查得到的任何錯誤,僅此而已。(當然,另乙個可能性是,編譯器或鏈結器有問題,但不要拿這種可能性來困擾我們。)

對於執行時錯誤來說,情況大不一樣。在某次執行期間程式沒有產生任何執行時錯誤,你就能確信另一次不同的執行期內不會產生錯誤嗎?比如:在另一次執行中,你以不同的順序做事,或者採用不同的資料,或者執行更長或更短時間,等等。你可以不停地測試自己的程式直到面色發紫,但你還是不能覆蓋所有的可能性。因而,執行時發現錯誤比在編譯鏈結期間檢查錯誤更不能讓人放心。

通常,對設計做一點小小的改動,就可以在編譯期間消除可能產生的執行時錯誤。這常常涉及到在程式中增加新的資料型別(參見技巧m33)。例如,假設想寫乙個類來表示時間中的日期,最初的做法可能象這樣:

class date ;

準備實現這個建構函式,面臨的乙個問題是對day和month值的合法性檢查。讓我們來看看,對於傳給month的值來說,怎麼做可以免於對它進行合法性檢查呢?

乙個明顯的辦法是採用列舉型別而不用整數:

enum month ;

class date ;

遺憾的是,這不會換來多少好處,因為列舉型別不需要初始化:

month m;

date d(22, m, 1857);      // m是不確定的

所以,date建構函式還是得驗證month引數的值。

既想免除執行時檢查,又要保證足夠的安全性,你就得用乙個類來表示month,你就得保證只有合法的month才被建立:

class month

static const month feb()

...static const month dec()

int asint() const           // 為了方便,使month

// 可以被轉換為int

private:

month(int number): monthnumber(number) {}

const int monthnumber;

};class date ;

這個設計在幾個方面的特點綜合確定了它的工作方式。首先,month建構函式是私有的。這防止了使用者去建立新的month。可供使用的只能是month的靜態成員函式返回的物件,再加上它們的拷貝。第二,每個month物件為const,所以它們不能被改變(否則,很多地方會忍不住將一月轉換成六月,特別是在北半球)。最後一點,得到month物件的唯一辦法是呼叫函式或拷貝現有的month(通過隱式month拷貝建構函式 ---- 見技巧45)。這樣,就可以在任何時間任何地方使用month物件;不必擔心無意中使用了沒有被初始化的物件。(否則就可能有問題。技巧47進行了說明)

有了這些類,使用者幾乎不可能指定乙個非法的month,甚至完全不可能 ---- 如果不出現下面這種可惡的情況的話:

month *pm;                 // 定義未被初始化的指標

date d(1, *pm, 1997);      // 使用未被初始化的指標!

但這種情況所涉及的是另乙個問題,即通過未被初始化的指標取值,其結果是不可確定的。(參見技巧3,看看我對 "不確定行為" 的感受)遺憾的是,我沒有辦法來防止或檢查這種異端行為。但是,如果假設這種情況永遠不會發生,或者如果我們不考慮這種情況下軟體的行為,date建構函式對它的month引數就可以免於合法性檢查。另一方面,建構函式還是必須檢查day引數的合法性 ---- 九月,四月,六月和十一月各有多少天呢?

date的例子將執行時檢查用編譯時檢查來取代。你可能想知道什麼時候可以使用鏈結時檢查。實際上,不是經常這麼做。c++用鏈結器來保證所需要的函式只被定義一次(參見技巧45,"需要" 乙個函式會帶來什麼)。它還使用鏈結器來保證靜態物件(參見技巧47)只被定義一次。你可以用同樣的方法使用鏈結器。例如,技巧27說明,對於乙個顯式宣告的函式,如果想有意禁止對它進行定義,鏈結器檢查就很有用。

但不要過於強求。想消除所有的執行檢查是不切實際的。例如,任何允許互動式輸入的程式都要進行輸入驗證。同樣地,某個類中如果包含需要執行上下限檢查的陣列,每次訪問陣列時就要對陣列下標進行檢查。儘管如此,將檢查從執行時轉移到編譯或鏈結時一直是值得努力的目標,只要實際可行,就要追求這一目標。這樣做的獎賞是,程式會更小,更快,更可靠。

條款46 寧願編譯和連線時出錯,也不要執行時出錯

1,先看乙個例子 class month static const month feb static const month dec int asint const for convenience,make it possible to convert a month to an int priva...

編譯時連線與執行時鏈結及靜態庫鏈結

本地編譯器動態庫編譯時鏈結 l lib usr lib usr local lib 編譯時鏈結的話,能查到libname.so 且這個檔案要不是個link,要不就是實際的動態庫檔案,否則會報錯 說鏈結順序沒有意義,因為並不鏈結到檔案裡面去 注意 不包括ld library path與 etc ld....

Java編譯時多型和執行時多型

編譯時多型 主要是方法的過載,通過引數列表的不同來區分不同的方法。執行時多型 也叫作動態繫結,一般是指在執行期間 非編譯期間 判斷引用物件的實際型別,根據實際型別判斷並呼叫相應的屬性和方法。主要用於繼承父類和實現介面時,父類引用指向子類物件。例如 public class polymorphismt...