1. 隱式類型轉(zhuǎn)換
C/C++中基本類型的自動類型轉(zhuǎn)換是經(jīng)常見到的情形, 例如下邊的代碼:
int i ;
float j ;
......
j = i;
其實(shí),這就是一種隱式類型轉(zhuǎn)換。當(dāng)把整形的 i 賦值給浮點(diǎn)型的 j 時黄橘,編譯器自動完成了轉(zhuǎn)換。 有了隱式類型轉(zhuǎn)換屈溉,我們少寫了很多代碼塞关,省去了不少麻煩。
2. 隱式類類型轉(zhuǎn)換
C++的類可以看作一種用戶自定義的數(shù)據(jù)類型子巾。既然和基本數(shù)據(jù)類型一樣帆赢,C++類也是一種數(shù)據(jù)類型小压,那么讓C++類支持隱式類型轉(zhuǎn)換就成了一種自然而然的想法。
在C++規(guī)則中, 可以調(diào)用單個實(shí)參的構(gòu)造函數(shù)定義了從形參類型到該類類型的一個隱式轉(zhuǎn)換椰于。
例如下邊的代碼:
class Student
{
public:
Student() { }
Student(int age) { }
};
class Teacher
{
public:
Teacher() { }
Teacher(int age, string name="unkown") { }
};
Student類的構(gòu)造函數(shù)僅需要一個實(shí)參就能構(gòu)造對象怠益。Teacher類的構(gòu)造函數(shù)有兩個參數(shù),但是由于第二個參數(shù)提供了默認(rèn)值瘾婿,也可以通過一個參數(shù)構(gòu)造對象蜻牢。因此,按照C++規(guī)則偏陪,這兩個類的構(gòu)造函數(shù)都實(shí)現(xiàn)了隱式轉(zhuǎn)換功能抢呆。
所以下邊的代碼也就完全可以編譯通過了:
Student foo;
Teacher bar;
foo = 12; //隱式轉(zhuǎn)換
bar = 40; //隱式轉(zhuǎn)換
上述的隱式類型轉(zhuǎn)換同樣由編譯器完成,編譯器通過構(gòu)造函數(shù)構(gòu)造出一個臨時對象笛谦,并將其復(fù)制為foo和bar抱虐。
這樣用起來似乎和基本類型的自動轉(zhuǎn)換一樣的爽快, 省去了好多代碼揪罕。然而梯码,凡事總是有兩面性,越是便利性的東西好啰,往往越容易犯錯。你怎么能夠總是保證編譯器幫你構(gòu)造出來的代碼就是你想要實(shí)現(xiàn)的邏輯呢儿奶?一旦出現(xiàn)代碼拼寫錯誤框往,代碼實(shí)現(xiàn)和自己的邏輯不符,而編譯器又不報錯闯捎,Debug時將加倍地費(fèi)時費(fèi)力椰弊。
關(guān)于隱式類類型轉(zhuǎn)換的缺點(diǎn),網(wǎng)上流傳比較廣泛的是如下的兩個例子:
例1:拼寫錯誤
Array<int> a[10];
Array<init>b[10];
for(int i=0;i<10;i++) {
if(a==b[i]) { //原意是a[i],現(xiàn)在出現(xiàn)了錯誤
//發(fā)生點(diǎn)什么
}
}
這個例子中省略了一部分代碼瓤鼻,原文請參閱參考文檔[1].
簡單講秉版,由于Array類中定義了一個可以只接收一個整形參數(shù)的構(gòu)造函數(shù), 因此在if(a==b[i])
發(fā)生拼寫錯誤的情況下茬祷, 編譯器并不會報錯清焕,而是自動的從b[i]隱式轉(zhuǎn)換出一個Array類型的臨時對象,并同a進(jìn)行比較祭犯。
例2:邏輯錯誤
// A simple class
class A {};
// Another simple class with a single-argument constructor for class A
class B
{
public:
B() {}
B(A const&) {}
};
// A function that expects a 'B'
void f(B const&) {}
int main()
{
A obj;
f(obj); // Spot the deliberate mistake
}
這個例子說邏輯錯誤似乎有點(diǎn)牽強(qiáng)秸妥,因為有人會講,我本來的邏輯就是要通過A類的對象構(gòu)造出來一個B類的對象沃粗,然后給f()函數(shù)使用粥惧。好吧,如果是這樣最盅,我們至少可以說邏輯不夠嚴(yán)謹(jǐn)吧突雪。至少大部分讀代碼的人會感到詫異起惕,我們要讓f()函數(shù)要處理的是一個B類對象,但是卻給了它一個A類的對象作為參數(shù)咏删,而且代碼編譯是完全沒有問題的疤祭。
之所以代碼編譯不出問題是因為B類包含了一個可以接受A類對象引用值作為參數(shù)的構(gòu)造函數(shù),滿足了隱式轉(zhuǎn)換規(guī)則饵婆。所以當(dāng)我們把A類對象obj作為參數(shù)送給f()函數(shù)時勺馆,編譯器調(diào)用了隱式轉(zhuǎn)換規(guī)則,生成了一個臨時的B類對象并傳遞給f()函數(shù)侨核。上述例子的完整表述草穆,可以參閱參考文檔[2].
你可能會想,隱式轉(zhuǎn)換也太強(qiáng)大了吧搓译,問題是悲柱,你是否能夠保證每次都是你有意寫出的代碼,而不是無心插柳些己?
3. Google C++編程規(guī)范對隱式類型轉(zhuǎn)換的建議
關(guān)于隱式類型轉(zhuǎn)換的優(yōu)缺點(diǎn)豌鸡,Google C++編程規(guī)范有詳細(xì)的描述。 其優(yōu)點(diǎn)就是語法簡潔段标,省事兒涯冠。缺點(diǎn)就比較多了:
- 容易隱藏類型不匹配的錯誤;
- 代碼更難閱讀逼庞;
- 接收單個參數(shù)的構(gòu)造方法可能會意外地被用做隱式類型轉(zhuǎn)換蛇更;
- 不能清晰地定義那種類型在何種那個情況下應(yīng)該被隱式轉(zhuǎn)換,從而使代碼變得晦澀難懂赛糟。
Google的結(jié)論就是可接收單個參數(shù)的構(gòu)造函數(shù)必需要加上explicit標(biāo)記派任,禁止隱式類類型轉(zhuǎn)換 (復(fù)制和移動構(gòu)造函數(shù)除外,因為這兩者不執(zhí)行類型轉(zhuǎn)換)璧南。
按照Google編程規(guī)范掌逛,上述例子中可接收單個參數(shù)的構(gòu)造函數(shù)都應(yīng)該聲明為如下形式。
class Student
{
public:
Student() { }
explicit Student(int age) { }
};
class Teacher
{
public:
Teacher() { }
explicit Teacher(int age, string name="unkown") { }
};
這樣將會禁止類類型的隱式轉(zhuǎn)換司倚,編譯器在編譯類似代碼時會直接報錯豆混,提醒開發(fā)人員檢查自己的代碼是否符合邏輯。