讓編譯器進行隱式類型轉換所形成的弊端要大於它所帶來的好處,因此除非你確實須要,不要定義類型轉換函數。數組
隱式類型轉換的缺點:它們的存在將致使錯誤的發生。
例如:
class Rational {
public:
...
operator double() const; // 轉換Rational類成double類型
};
在下面這種狀況下,這個函數會被自動調用:
Rational r(1, 2); // r 的值是1/2
double d = 0.5 * r; // 轉換 r 到double,而後作乘法
假設你有一個如上所述的Rational類,你想讓該類擁有打印有理數對象的功能,就好像它是一個內置類型。所以,你可能會這麼寫:
Rational r(1, 2);
cout << r; // 應該打印出"1/2"
當編譯器調用operator<<時,會發現沒有這樣的函數存在,可是它會試圖找到一個合適的隱式類型轉換順序以使得函數調用正常運行。類型轉換順序的規則定義是複雜的,可是在如今這種狀況下,編譯器會發現它們能調用Rational::operatordouble函數來把r轉換爲double類型。因此上述代碼打印的結果是一個浮點數,而不是一個有理數。這簡直是一個災難,可是它代表了隱式類型轉換的缺點:它們的存在將致使錯誤的發生。函數
解決方法是用不使用語法關鍵字的等同的函數來替代轉換運算符。例如爲了把Rational對象轉換爲double,用asDouble函數代替operator double函數:
class Rational {
public:
...
double asDouble() const; //轉變 Rational
}; // 成double
這個成員函數能被顯式調用:
Rational r(1, 2);
cout << r; // 錯誤! Rationa對象沒有operator<<
cout << r.asDouble(); // 正確, 用double類型打印r對象
經過不聲明運算符(operator)的方法,能夠克服隱式類型轉換運算符的缺點,可是單參數構造函數沒有那麼簡單。畢竟,你確實想給調用者提供一個單參數構造函數。同時你也但願防止編譯器不加鑑別地調用這個構造函數。幸運的是,有一個方法可讓你魚肉與熊掌兼得。事實上是兩個方法:一是容易的方法,二是當你的編譯器不支持容易的方法時所必須使用的方法。
容易的方法是利用一個最新編譯器的特性,explicit關鍵字。爲了解決隱式類型轉換而特別引入的這個特性,它的使用方法很好理解。構造函數用explicit聲明,若是這樣作,編譯器會拒絕爲了隱式類型轉換而調用構造函數。顯式類型轉換依然合法:
template<class T>
class Array {
public:
...
explicit Array(int size); // 注意使用"explicit"
...
};
Array<int> a(10); // 正確, explicit 構造函數在創建對象時能正常使用
Array<int> b(10); // 也正確
if (a == b[i]) ... // 錯誤! 沒有辦法隱式轉換int 到 Array<int>
if (a == Array<int>(b[i])) ... // 正確,顯式從int到Array<int>轉換(可是代碼的邏輯不合理)
if (a == static_cast< Array<int> >(b[i])) ... //一樣正確,一樣不合理
if (a == (Array<int>)b[i]) ... //C風格的轉換也正確,可是邏輯 依舊不合理
在例子裏使用了static_cast(參見條款M2),兩個「>」字符間的空格不能漏掉,若是這樣寫語句:
if (a == static_cast<Array<int>>(b[i])) ...
這是一個不一樣的含義的語句。由於C++編譯器把「>>」作爲一個符號來解釋。在兩個「>」間沒有空格,語句會產生語法錯誤。
若是你的編譯器不支持explicit,你不得不回到不使用成爲隱式類型轉換函數的單參數構造函數。
我前面說過複雜的規則決定哪個隱式類型轉換是合法的,哪個是不合法的。這些規則中沒有一個轉換可以包含用戶自定義類型(調用單參數構造函數或隱式類型轉換運算符)。你能利用這個規則來正確構造你的類,使得對象可以正常構造,同時去掉你不想要的隱式類型轉換。
再來想一下數組模板,你須要用整形變量作爲構造函數參數來肯定數組大小,可是同時又必須防止從整數類型到臨時數組對象的隱式類型轉換。你要達到這個目的,先要創建一個新類ArraySize。這個對象只有一個目的就是表示將要創建數組的大小。你必須修改Array的單參數構造函數,用一個ArraySize對象來代替int。代碼以下:
template<class T>
class Array {
public:
class ArraySize { // 這個類是新的
public:
ArraySize(int numElements): theSize(numElements) {}
int size() const { return theSize; }
private:
int theSize;
};
Array(int lowBound, int highBound);
Array(ArraySize size); // 注意新的聲明
...
};
這裏把ArraySize嵌套入Array中,爲了強調它老是與Array一塊兒使用。你也必須聲明ArraySize爲公有,爲了讓任何人都能使用它。
想一下,當經過單參數構造函數定義Array對象,會發生什麼樣的事情:
Array<int> a(10);
你的編譯器要求用int參數調用Array<int>裏的構造函數,可是沒有這樣的構造函數。編譯器意識到它能從int參數轉換成一個臨時ArraySize對象,ArraySize對象只是Array<int>構造函數所須要的,這樣編譯器進行了轉換。函數調用(及其後的對象創建)也就成功了。
若是你沒有Array(ArraySize size) 構造函數,在某些狀況下也是有利的。考慮一下如下代碼:
bool operator==( const Array<int>& lhs,
const Array<int>& rhs);
Array<int> a(10);
Array<int> b(10);
...
for (int i = 0; i < 10; ++i)
if (a == b[i]) ... // 哎呦! "a" 應該是 "a[i]";
// 如今是一個錯誤。
爲了調用operator==函數,編譯器要求Array<int>對象在」==」右側,可是若是不存在一個參數爲int的單參數構造函數,那麼編譯器就沒法把int轉換成一個臨時ArraySize對象而後經過這個臨時對象創建必須的Array<int>對象,因此當試圖進行比較時編譯器確定會產生錯誤。ci