類别:庫作者
在c++中,有個非常好也非常壞的特性,就是隐式類型轉換。隐式類型轉換的“自動性”可以讓程式員免于層層構造類型。但也是由于它的自動性,會在一些程式員意想不到的地方出現嚴重的但不易被發現的錯誤。我們可以先看看代碼清單3-26所示的這個例子。
在代碼清單3-26中,聲明了兩個類型rational1和rational2。兩者在代碼上的差別不大,隻不過rational1的構造函數rational1(int,int)沒有explicit關鍵字修飾,這意味着該構造函數可以被隐式調用。是以,在定義變量r1_1的時候,字面量11就會成功地構造出rational1(11, 1)這樣的變量,rational2卻不能從字面量21中構造,這是因為其構造函數由于使用了關鍵字explicit修飾,禁止被隐式構造,是以會導緻編譯失敗。相同的情況也出現在函數display2上,由于字面量2不能隐式地構造出rational2對象,是以表達式display2(2)的編譯同樣無法通過。
這裡雖然display1(1)編譯成功,不過如果不是結合了上面rational1的定義,我們很容易在閱讀代碼的時候産生誤解。按照習慣,程式員會誤認為display1是個列印整型數的函數。是以,使用了explicit這個關鍵字保證對象的顯式構造在一些情況下都是必須的。
不過同樣的機制并沒有出現在自定義的類型轉換符上。這就允許了一個逆向的過程,從自定義類型轉向一個已知類型。這樣雖然出現問題的幾率遠小于從已知類型構造自定義類型,不過有的時候,我們确實應該阻止會産生歧義的隐式轉換。讓我們來看看代碼清單3-27所示的例子,該例子來源于c++11提案。
在代碼清單3-27中,我們定義了一個指針模闆類型ptr。為了友善判斷指針是否有效,我們為指針編寫了自定義類型轉換到bool類型的函數,這樣一來,我們就可以通過if(p)這樣的表達式來輕松地判斷指針是否有效。不過這樣的轉換使得ptr和ptr兩個指針的加法運算獲得了文法上的允許。不過明顯地,我們無法看出其語義上的意義。
在c++11中,标準将explicit的使用範圍擴充到了自定義的類型轉換操作符上,以支援所謂的“顯式類型轉換”。explicit關鍵字作用于類型轉換操作符上,意味着隻有在直接構造目标類型或顯式類型轉換的時候可以使用該類型。我們可以看看代碼清單3-28所示的例子。
在代碼清單3-28中,我們定義了兩個類型convertto和convertable,convertable定義了一個顯式轉換到convertto類型的類型轉換符。那麼對于main中convertto類型的ct變量而言,由于其直接初始化構造于convertable變量c,是以可以編譯通過。而做強制類型轉換的ct3同樣通過了編譯。而ct2由于需要從c中拷貝構造,因而不能通過編譯。此外,我們使用函數func的時候,傳入convertable的變量c的也會導緻參數的拷貝構造,是以也不能通過編譯。
如果我們把該方法用于代碼清單3-27中,可以發現我們預期的事情就發生了,if(p)可以通過編譯,因為可以通過p直接構造出bool類型的變量。而p + pd這樣的語句就無法通過編譯了,這是由于全局的operator + 并不接受bool類型變量為參數,而convertable也不能直接構造出适用于operator +的int類型的變量造成的(不過讀者可以嘗試一下使用p && pd這樣的表達式,是能夠通過編譯的)。這樣一來,程式的行為将更加良好。
可以看到,所謂顯式類型轉換并沒完全禁止從源類型到目标類型的轉換,不過由于此時拷貝構造和非顯式類型轉換不被允許,那麼我們通常就不能通過指派表達式或者函數參數的方式來産生這樣一個目标類型。通常通過指派表達式和函數參數進行的轉換有可能是程式員的一時疏忽,而并非本意。那麼使用了顯式類型轉換,這樣的問題就會暴露出來,這也是我們需要顯式轉換符的一個重要原因。