天天看點

More Effective C++ 05:謹慎定義類型轉換函數

C++編譯器允許把

char

隐式轉換為

int

和從

short

隐式轉換為 double。是以當你把一個 short 值傳遞給準備接受 double 參數值的函數時,依然可以成功運作。

你對這些類型轉換是無能為力的,因為它們是語言本身的特性。不過當你增加自己的類型時,你就可以有更多的控制力,因為你能選擇是否提供函數讓編譯器進行隐式類型轉換。

有兩種函數允許編譯器進行這些的轉換:單參數構造函數和隐式類型轉換運算符。單參數構造函數是指隻用一個參數即可以調用的構造函數。該函數可以是隻定義了一個參數,也可以是雖定義了多個參數但第一個參數以後的

所有參數都有預設值。以下有兩個例子:

class Name 
{ 
public: 
 Name(const string& s); // 轉換 string 到 Name
 ... 
}; 
class Rational  // 有理數類 
{
public: 
	Rational(int numerator = 0,int denominator = 1); //轉換 int 到有理數類   
	
 ...
};
           

例如為了允許

Rational

(有理數)類隐式地轉換為

double

類型你可以如此聲明 Rational 類:

class Rational 
{ 
public: 
 ... 
	operator double() const; // 轉換 Rational類成double 類型
};
           

在下面這種情況下,這個函數會被自動調用:

Rational r(1, 2);
double d = 0.5 * r;// 轉換 r 到 double然後做乘法。
           

假設你有一個如上所述的

Rational

類,你想讓該類擁有列印有理數對象的功能,就好像它是一個内置類型。是以,你可能會這麼寫:

Rational r(1, 2); 
cout << r; // 應該列印出"1/2"
           

假設你忘了為

Rational

對象定義

operator<<

。你可能想列印操作将失敗,因為沒有合适的的

operator<<

被調用。但是你錯了。當編譯器調用

operator<<

時,會發現沒有這樣的函數存在,但是它會試圖找到一個合适的隐式類型轉換順序以使得函數調用正常運作。類型轉換順序的規則定義是複雜的,但是在現在這種情況下,編譯器會發現它們能調用

Rational::operator double

函數來把 r 轉換為double類型。是以上述代碼列印的結果是 一個浮點數,而不是一個有理數

More Effective C++ 05:謹慎定義類型轉換函數

解決方法:

用不使用文法關鍵字的等同的函數來替代轉換運算符。例如為了把

Rational

對象轉換為

double

,用

asDouble

函數代替

operator double

函數:

More Effective C++ 05:謹慎定義類型轉換函數
More Effective C++ 05:謹慎定義類型轉換函數

在多數情況下,這種顯式轉換函數的使用雖然不友善,但是函數被悄悄調用的情況不再會發生,這點損失是值得的。一般來說,越有經驗的C++程式員就越喜歡避開類型轉換運算 符。

比如的

string

類型沒有包括隐式地從

string

轉換成C風格的

char*

的功能,而是定義了一個成員函數

c_str

用來完成這個轉換。

通過單參數構造函數進行隐式類型轉換更難消除。而且在很多情況下這些函數所導緻的問題要甚于隐式類型轉換運算符。

比如:

template<class T> 
class Array 
{ 
public: 
	 Array(int lowBound, int highBound); 
	 Array(int size); 
	 T& operator[](int index); 
 ... 
};
           

第一個構造函數允許調用者确定數組索引的範圍,例如從 10 到 20。它是一個兩參數構造函數,是以不能做為類型轉換函數。

第二個構造函數讓調用者僅僅定義數組元素的個數(使用方法與内置數組的使用相似),不過不同的是它能做為類型轉換函數使用,能導緻無窮的痛苦。

例如比較 Array對象,部分代碼如下:

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]" 
 { 
	 do something for when 
	 a[i] and b[i] are equal; 
 } 
 else 
 { 
	 do something for when they're not; 
 }
           

我們想用 a 的每個元素與 b 的每個元素相比較,但是當錄入 a 時,我們偶然忘記了數組下标。當然我們希望編譯器能報出各種各樣的警告資訊,但是它根本沒有。因為它把這個調用看成用

Array<int>

參數(對于 a)和

int

(對于 b[i])參數調用

operator==

函數

然而沒有

operator==

函數是這樣的參數類型,我們的編譯器注意到它能通過調用

Array<int>

構造函數能轉換

int

類型到

Array<int>

類型,這個構造函數隻有一個

int

類型的參數。然後編譯器如此去編譯,生成的代碼就象這樣:

for (int i = 0; i < 10; ++i) 
 if (a == static_cast<Array<int>>(b[i])) ...
           

每一次循環都把a的内容與一個大小為 b[i]的臨時數組(内容是未定義的)比較。這不僅不可能以正确的方法運作,而且還是效率低下的。因為每一次循環我們都必須建立和釋 放

Array<int>

對象

容易的方法是利用一個最新編譯器的特性,

explicit

關鍵字。為了解決隐式類型轉換而特别引入的這個特性,它的使用方法很好了解。構造函數用

explicit

聲明,如果這樣做,編譯器會拒絕為了隐式類型轉換而調用構造函數。顯式類型轉換依然合法:

template<class T> 
class Array 
{ 
public: 
... 
	explicit Array(int size);
... 
};
           

總結:

讓編譯器進行隐式類型轉換所造成的弊端要大于它所帶來的好處,是以除非你确實需要,不要定義類型轉換函數

繼續閱讀