之前看過一些批判C++的文章,大致意思是它包含了太多的“奇技淫巧”,并不是一門好的語言。我對這個“奇技淫巧”的描述頗感興趣,因為按照批判者的說法,C++的一些特性恰巧可以讓一些炫耀技術的同學有了炫耀的資本——畢竟路人皆知的東西卻沒什么好炫耀的。這又讓我想起了《孔乙己》中關于“回”字有幾種寫法的描述。當時老師在上此課時,是抱著批判的態度去評價孔乙己的這種思想,而我卻感覺到這其中必有一些有意思的文化在里面——或許是“回”字演變的歷程能說明什么。所以“回”字相關的內容讓我感覺孔乙己是個純粹和可愛的人。
寫這個系列的博文,并不是我想對C++進行什么批判,也不是想對其進行辯護。只是想羅列一些有意思的東西,故取名拾趣。
首先我們看下一種比較常見的技術——類構造函數的隱式轉換。這兒先說明下,之后的例子中,我會為了盡量突出主要內容,而忽略一些可以作為充分條件但非必要條件的東西,故設計的一些代碼存在“不完善”的嫌疑。因為為了堵住所有漏洞,往往會讓整個代碼讓人感覺其重心并非在我想介紹的技術上,而在“苦行僧”式的編程原則上。
我們知道C++是一個類型嚴格的語言,比如下面一個函數
void test_int_PRoxy(const int_proxy& v) { printf("%d", v.value());} 調用者對其傳參也應該是一個int_proxy的對象,但是實際情況并非如此。那該如何表述,我個人覺得應該是:編譯器對其傳參應該是一個int_proxy對象。這兩種表述的區別就是“調用者”和“編譯器”的區別。我們來看一個實際例子,我們先假定int_proxy類這么定義:class int_proxy {public: int_proxy(int n) : _m(n) {};public: int value() const { return _m; }private: int _m;}; 該類非常簡單,它有一個帶參數的構造函數,并使用參數列表形式初始化類的成員變量。 一般情況下我們都會這么調用test_int_proxy方法:
test_int_proxy(int_proxy(100)); 這種寫法我想沒人會有異議,但是如果出現下面這種寫法,就可能讓人感覺不可接受了: test_int_proxy(100); 然而,這種寫法對上述類的定義來說是合法的!其效果和使用int_proxy控制住是一樣的。這是為什么呢?這便是類構造函數的隱式轉換技術。C++編譯器認為test_int_proxy方法傳入的應該是一個const類型的int_proxy對象,然而如果它發現參數不是該對象時,就會使用該類中可以使用該參數進行構造對象的方法構造出一個臨時的對象。我們例子中傳參100是個int型數據,而int_proxy正好有一個攜帶int參數的構造函數。稍微總結下類構造函數隱式轉換的必要條件:找不到傳參類型嚴格對應的函數找到傳參類型嚴格匹配的類的構造函數因為隱式轉換構造出的是臨時對象,所以不可修改,故觸發隱式轉換的函數的傳參類型必須要使用const修飾 但是個人覺得這種“奇巧淫技”還是不用為好。比如我們代碼中還有如下函數:void test_int_proxy(const int& v) { printf("%d", v + 100);} 那么C++編譯器會針對傳100的調用上面這個過程。這樣一個函數調用有兩個匹配的調用方法就會產生不確定性——這兒指的不確定性并非是指編譯器調用哪個方法的不確定性,而是指維護這段代碼的人對上述代碼做調整時容易忽略一些問題而導致的“人禍”。 再比如,我們在代碼中加入下面類和方法
class int_proxy_2 {public: int_proxy_2(int n) : _m(n) {};public: int value() const { return _m + 100; }private: int _m;};void test_int_proxy(const int_proxy_2& v) { printf("%d", v.value());} 那么編譯器不能確定隱式轉換是要轉換哪個類,更不知道是調用哪個test_int_proxy方法了。 限制類構造函數的隱式轉換的方法也很簡單,就是給對應的構造函數加上explict關鍵字class int_proxy {public: explicit int_proxy(int n) : _m(n) {}; 這樣通過隱式轉換而構造臨時對象的圖謀將會被察覺并禁止。
新聞熱點
疑難解答
圖片精選