天天看點

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

本節書摘來自異步社群出版社《c++程式設計調試秘笈》一書中的第2章,第2.3節,作者: 【美】vladimir kushnir, 【德】nicolai m. josuttis,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

c++程式設計調試秘笈

現在,如果我們建立一個time類,在内部實作中隐藏了從什麼時間開始,以及用什麼時間機關(秒、毫秒等)進行測量等細節,上面這些雜七雜八的問題就可以輕松得以避免。這種方法的一個優點是如果我們錯誤地傳遞了其他日期資料,而不是傳遞了時間(現在用time類型表示),編譯器馬上就能捕捉到這種錯誤。這種方法的另一個優點是,如果time類目前是用毫秒實作的,并且以後為了提高精度用微秒表示,我們隻需要編輯一個類,修改内部實作的細節,而不會影響其餘的代碼。

是以,我們怎樣才能在編譯時而不是在運作時捕捉類型錯誤呢?我們首先可以用一個單獨的類表示每種類型的資料。我們用int表示整數,用double表示浮點數,用std::string表示文本,用date表示日期,用time表示時間,對于其他類型的資料也都用一個單獨的類表示。但是,隻采用這種做法仍然是不夠的。假設我們有兩個類apple和orange,并有一個期望接受一個orange類型的參數的函數:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

但是,我們可能不小心向它提供了apple類型的對象:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

在有些情況下,這樣的代碼可以通過編譯,因為c++編譯器試圖向我們提供幫助。隻要可能,它會把apple平靜地轉換為orange。這可能通過以下兩種方式發生。

(1)如果orange類具有一個隻接受一個apple類型的參數的構造函數。

(2)如果apple類具有一個可以把它轉換為orange的操作符。

當orange類具有下面這樣的定義時,就會發生第一種情況:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

它甚至可以像下面這樣:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

即使在最後這個例子中,構造函數看上去像是具有兩個輸入,但它也可以隻用一個參數就可以被調用,是以它也可以隐式地把apple轉換為orange。這個問題的解決方案是用關鍵字explicit聲明這類構造函數。這種做法可以防止編譯器執行自動(隐式)轉換,這樣我們就可以迫使程式員在期望接受orange的地方必須使用orange:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

第二個例子需要對應地修改為:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

另一種讓編譯器知道怎麼把apple轉換為orange的方法是提供一個轉換操作符:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

這個操作符在此處的出現是非同尋常的,說明程式員用一種明确的方式向編譯器提供了一種把apple轉換為orange的方法,它并不是什麼錯誤。是以,對所有接受一個參數的構造函數用關鍵字explicit進行聲明,這是值得推薦的做法。一般而言,隐式轉換的所有可能性都是不好的思路。是以,如果想按照上面這個例子一樣在apple類中提供一種把apple轉換為orange的方法,下面是一種更好的方法:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

在這個例子中,為了把apple轉換為orange,需要采用下面的方式:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

另外還有一種方法可以混合不同的資料類型,即使用枚舉(enum)。考慮下面這個例子:假設我們定義了下面這兩個枚舉,分别表示一周中的某天以及月份。

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

這些常量實際上都是整數(例如,c内置的int類型)。如果我們有一個期望接受一周中的某天作為參數的函數:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

下面這個調用将會在不産生任何警告的情況下通過編譯:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

在運作時,我們能夠采取的措施不多,因為jan和mon都是與1相等的整數。捕捉這類缺陷的方法是不使用建立整數的“單純功能”枚舉,而是使用建立新類型的枚舉:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

在這種情況下,期望接受一周中的某天為參數的函數将被聲明為:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

像下面這樣試圖用一個month值調用這個函數:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

将會産生編譯錯誤:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

這正是我們在這個例子中期待産生的效果。

但是,這種方法具有一個消極因素。在這個例子中,用枚舉建立整型常量時,我們可以編寫如下的代碼:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

但是當我們使用枚舉建立新類型時,如下面的寫法:

《C++程式設計調試秘笈》——2.3 處理類型的正确方式

就無法通過編譯。是以,如果我們需要疊代枚舉的值,可以像原來一樣使用整數。

當然,任何規則都有例外,有時候程式員有理由編寫像variant這樣的類,允許進行隐式類型轉換,以滿足特定的需要。但是,絕大多數時候應該完全避免隐式類型轉換,這就允許我們充分利用編譯器檢查不同變量類型的功能,早期(即在編譯時)捕捉潛在的錯誤。

現在,假設我們已經盡自己所能使用了類型安全。遺憾的是,除了bool和char類型之外,每種類型可能包含的值的數量都是天文數字,通常隻有一小部分值是合理的。例如,如果我們使用double類型表示股票的價格,可以很合理地确定股票的價格将在0到10 000之間波動(唯一的例外是berkshire hathaway公司的股票,它

的主人warren buffet顯然并不相信把股票價格保持在合理範圍内是個好主意,是以他從不對股票進行除權,在本書寫作之時這個股票的價格是每股10萬美元)。但即使是berkshire hathaway這樣的股票,它的價格仍然隻使用了double類型的很小一部分,因為double的範圍高達10308,并且還包含了完全不适合表示股票價格的負數。由于大多數類型隻有一小部分值是合理的,是以總是存在一些隻能在運作時才能診斷的錯誤。

事實上,c語言的大多數問題,例如指定了越界索引,或通過指針運算不恰當地通路内容,隻能在運作時才能得到診斷。由于這個原因,本書的剩餘部分主要專注于讨論捕捉運作時錯誤。

本章所讨論的在編譯時診斷錯誤的規則如下。

禁止隐式類型轉換:用關鍵字explicit聲明一個接受1個參數的構造函數,并避免使用轉換操作符。

用不同的類表示不同的資料類型。

不要使用枚舉建立整型常量,而是用它們建立新類型。

本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。

繼續閱讀