有必要在寫 C++ 時避免隱式轉換嗎?
題主在 stackoverflow 上 發過如下一段代碼
pair&
getCost(const string str) const {
int cnts[] = {0, 0};
for (char c : str) ++cnts[static_cast&(c == "1")];
return {cnts[0], cnts[1]};
}
網友 `M.M` 提到代碼中的 `static_cast` 是多餘的,但我認為這通過顯示轉換,避免了從 `bool` 到 `size_t` 的隱式轉換。@藍色 也在 C/C++ 中 0 與 NULL 區別是什麼? 建議用有類型的 nullptr 而非 0. 我謹認為這和我的觀點有相似之處。
我的問題是:有必要在寫 C++ 時避免隱式轉換嗎?補充:
有朋友提到用三目運算符。因為我不確定編譯器是否一定能優化掉三目運算符 —— 若沒優化,那麼條件判斷流水性的性能損耗是否會更大一些 —— 所以我在這沒有使用。
如果顯式轉換可以避免二義性或者提升可讀性就可以顯式轉換,沒必要在所有有轉換的地方都顯式轉換。另外你的這段代碼其實沒有必要搞這樣的轉換,這樣寫就可以:
pair&
{
int true_count = 0;
int false_count = 0;
for (char c : str)
c == "1" ? true_count++ : false_count++;
return {false_count, true_count};
}
或者這樣:
//需要包含 & 頭文件
pair&
{
int true_count = count(str.begin(), str.end(), "1");
return {str.size() - true_count, true_count};
}
說個相關的,char 用作數組下標時,一定要先轉 unsigned char (因為 char 通常是有符號的)。不能直接轉 int 或 unsigned int ,會數組下標越界。Ideone.com
問有沒有「必要」,萬全的回答肯定是「看情況」。具體到題目里這段代碼,我覺得加上 static_cast 比不加上好,因為方便閱讀。
當你閱讀代碼第三行的時候,會發生些什麼可怕的事情?for (char c : str) ++cnts[c == "1"];
- 這裡有個 for 循環(哦哦,這裡是針對字元串里的每個字元 / 位元組在做處理)
- 循環體只有一行(好的,這是一種特殊情況,沒有加大括弧,下一行不屬於循環體)
- 布爾值被用作數組索引(沒錯,發生了隱式的類型轉換,這個字元如果是 1 就是 1、否則是 0)
- 前置 ++(這是什麼意思來著?和後置 ++ 有什麼區別來著?)
看到這些不覺得累嗎?這麼多信息集中在同一行,沒有明顯的標識,閱讀代碼心理壓力很大。
如果加上 static_cast 這麼突兀的東西:
for (char c : str) ++cnts[static_cast&
視覺上就把這一行代碼分割成了獨立的兩部分,數組索引的轉換邏輯被醜陋的語法與外界隔開了。所以看上去增加了(可能看上去多餘的轉換)代碼,但實際上降低了閱讀代碼時候的心理負擔。
當然讀起來更舒服的做法是不要把這麼多無關的邏輯塞到同一行:for (char c : str) { // 循環
size_t slot = (c == "1"); // 根據 c 是否為 1 選擇索引
cnts[slot] += 1; // 相信編譯器,避免 ++ 前後置帶來的閱讀負擔
}
隱式轉換會造成一些意想不到的情況出現,我就曾近見過有人將一個類定義隱式轉換成其Member Data的指針,然後有人用的時候寫了個if (xxx)順利的通過了編譯(因為轉成了指針又被轉成了bool),這種情況編譯器查不出來。。
還有,想想STL中容器的data(),std::basic_string&考慮有沒有必要時,不要。Less is better(除非是多線程考慮要不要加鎖時,加了再說)
如果是已經進入優化階段,就分析性能瓶頸。像為什麼 C++ 只比 VBA 快 4倍?這個提問里,性能瓶頸之一就是每一次循環都發生了類型轉換,所以將其修改。如果性能瓶頸不在這裡,那不需要管。多說多錯,多寫多Bug(逃C++的轉換規則遠比你想像的噁心得多。
不同類型間賦值和二元運算時都可能涉及integral promotions[4.5],integral conversions[4.7]
舉個例子:cout &<&< boolalpha &<&< (-1 == (unsigned char) -1) &<&< endl;
cout &<&< boolalpha &<&< (-1 == (unsigned short) -1) &<&< endl;
cout &<&< boolalpha &<&< (-1 == (unsigned int) -1) &<&< endl;
cout &<&< boolalpha &<&< (-1 == (unsigned long) -1) &<&< endl;
false
false
true
true
-1 == (unsigned char) -1
-1 == (unsigned short) -1
promote的過程很有意思,編譯器看到你說-1是char,那他就認為這個-1在promote前是0xFF,又看到你說這個char是unsigned的,那麼他promote成int時就直接在前面填零,即0x000000FF。(如果你要說它是個signed char的-1,它會給你promote成0xFFFFFFFF)
接下來就看這一個,現在右邊已經和左邊在比特層面都是32位了,要進行比較的時候問題又來了
-1 == (unsigned char) -1
當左邊的負數和右邊的unsigned作比較時,到底轉換誰?標準規定此時將負數轉成unsigned,也就是左邊的int型-1被當成unsigned對待,值就是,那麼和右邊0x000000FF的值255比較,當然兩者不相等。
short的那個同理。最後兩個,即int的-1和long的-1,由於沒有從小整型到大整型promotion的這一步,所以比較結果就為true。
上面說的這些東西大可以什麼都不知道,只要平時不要作死在signed和unsigned之間賦值或者進行比較。另外想說的是不要在C++中用C cast了,也即所謂的強制類型轉換,而應該用C++的const_cast, static_cast和reinterpret_cast。因為C casts在C++中是會被替換成這三種cast的組合的,而你不知道什麼時候就會被reinterpret_cast了,reinterpret_cast對於沒有經驗的人是很危險的,所以說,用C的強制類型轉換會產生無法預料的結果。避免隱式類型轉換很多時候是有必要的。假設我有一個容器類,他有一個構造函數接受一個size_t來初始化容器大小。template&
class Container
{public: Container(); Container(size_t size); T operator[](size_t index); bool operator==(const Container c)const; /*......*/};注意第二個構造函數有兩個特點:1.只含有一個參數
2.構造函數沒有被聲明為explicit這意味著這個構造函數支持將size_t類型隱式轉換為Container,那麼一下代碼就可以通過編譯:Container&c++不用隱式轉換很多泛型都不知道怎麼寫了...
個人感覺,很有必要...比如在進行對象初始化的時候,舉個例子來說吧,某個類的有整型參數的構造函數,我們可以直接給該類的對象賦一個整數,這樣就是一種隱式轉換。這在實際開發中,應用比較靈活,也很危險,為了解決這種隱式轉換帶來的問題,c++為類的構造函數提供了explicit關鍵字,避免這種隱式轉換髮生.......(手機打字好辛苦,蒼天啊.....
當然有必要啊,這樣的話有信息丟失的話還比較好找到,能避免就避免吧
類型轉換是危險的操作,使用更長的操作符提醒同事和將來的自己注意安全。
我覺得沒必要為了使用某些特性而去使用某些特性。比如你現在使用的static_cast ,明明有更好的解決方案。你要明白使用這些特性是要帶來一定效率上的開銷的,要不然為什麼說更安全呢?對不?
C++向下兼容C,你用不用顯式轉換都可以啊…這更像是一種編程風格問題。不過用顯示轉換更加安全,具體可以看這個帖子:In C++, why use static_cast&
推薦閱讀:
※C++中如何載入100K+的常數數組?
※C++自學用書推薦?
※自學計算機圖形學要哪些基礎?只用c語言可以嗎?
※long Rq = 1432567; int *x; x = (int *)Rq; printf("%d",*x); 錯誤在哪裡?
※為什麼說:不要使用 dynamic_cast, 需要運行時確定類型信息, 說明設計有缺陷?