C++是一個編譯器會替你在背后做很多事情的語言,包括模板實例化,按需要創造隱式的構造函數,默認構造你沒有顯式構造的成員,按需進行隱式轉換和飲食構造等等,如果沒有徹底了解清楚,就容易被這些編譯器背后做好的事情坑到,這個系列文章就來總結我在寫C++時遇到的各種坑。
所謂隱式調用和默認實現的構造函數,當你寫一個賦值語句的時候,編譯器會首先檢查兩個類型又沒有直接實現的賦值函數,然后檢查賦值左右的類型是否能做隱式轉換和構造,轉換或者構造好之后,再嘗試進行拷貝或移動賦值。這時候,坑點來了,如果你有這么樣的一個類:
struct Token { int label; string content; Token(int _label = -1, string _content = "") : label(_label) , content(_content) {} };
看它的構造函數,每一個參數都有默認參數,這個東西是會被編譯器當成默認構造函數的,並且這個構造函數不會被視為用戶自己實現的構造函數,所以編譯器依然會按需自動生成其他的[拷貝|移動][構造|賦值]函數,所以這時候坑點來了,以下的語句是合法的:
Token label(1, "hello"); label = 2; //這tm是合法的!!!
label = 2,這條語句會讓編譯器隱式調用Token的構造函數用2構造一個Token,參數的_content采用默認值“”,然后又調用隱式生成的移動賦值(move assignment)函數,進行賦值。而且毫無警告發生,這樣寫可能還比較明顯,容易發現問題,如果代碼復雜起來,被坑的可能性就大大提高了,我自己的tokenizer generator就是有一個bug坑在了這個地方,幸虧IDE能識別出運算符重載以及顯示鼠標指向的變量的值,讓我很快發現了這個bug,不然又不知道要debug到猴年馬月去了。這個問題還說明了使用一個牛逼的IDE的重要性,倘若是用VC6……呵呵呵呵呵。
話說我該把這個構造函數聲明為explicit的,忘了