前段時間在嘗試使用一個C++的GUI庫nana。這個庫最大的特點在於使用現代C++風格去編寫GUI程序,而不需要使用大量的比較丑陋的代碼(如MFC中的各種宏),或者其它的非C++元素。這是一個比較新的庫,作者是個中國人,有興趣的朋友可以去試一試,由於使用大量的C++11特性,所以需要VC2013或者GCC4.7以上的編譯器。使用過程中無意間發現了VC2013的一個重載決議(overload resolution)上的一個bug,這邊貼出來跟大家分享一下,或許可以幫助大家少走點彎路。
我寫了以下簡單的代碼來重現這個問題:
#include <iostream> #include <string> class foo{ public: void bar(std::string = {}); }; void foo::bar(std::string str) { std::cout << str << std::endl; } int main() { foo f; f.bar(); }
編譯運行這段代碼,彈出一個錯誤:
按照錯誤提示,可以找到對應的代碼1168行:
也就是這一行:
_DEBUG_POINTER(_Ptr);
加個斷點可以發現這里面_Ptr為空指針,所以導致程序crash。這個問題其實比較容易理解,寫一個更簡單的例子就可以發現問題:
#include <string> int main() { std::string str = nullptr; }
這段代碼通過nullptr去初始化一個string,而這顯然是錯誤的。
但問題在於,在最初的那段代碼中,
void bar(std::string = {});
這個函數聲明明明是給了函數bar一個默認參數,使其構造出一個空字符串。但為什么會調用xstring中的1168行呢?也就是參數為一個指針的assign函數呢?
經過多次試驗以后發現,VC將上面這個聲明理解為了:
void bar(std::string = {nullptr});
也就是用一個空指針去初始化一個string,從而導致了問題。同樣的代碼,在GCC4.8中是沒有任何問題的。並且如果把
void bar(std::string = {});
改為:
void bar(std::string = std::string());
在VC2013里也沒有問題了。這也就說明了VC2013在處理{}來初始化對象時的確存在重載決議的問題。
之所以之前說這個問題比較隱蔽,是因為這種情況只出現在類的成員函數中,如果是普通函數,就沒有這個問題:
#include <iostream> #include <string> void func(std::string = {}); int main() { func(); } void func(std::string str) { std::cout << str << std::endl; }
上面這段代碼和剛開始那段代碼幾乎一樣,唯一的不同在於func是一個普通函數,而bar是一個成員函數。
不知道大家有沒有遇到類似的問題?