Visual C++ 的重大變更
當您升級到 Visual C++ 編譯器的新版本時,先前正常編譯及執行的程式碼可能會發生編譯和/或執行階段錯誤。新版本中會造成這類問題的變更稱為「重大變更」(Breaking Change),在進行 C++ 語言標準、函式簽章或記憶體內部物件配置的修改時通常都會有重大變更。
在 Visual C++ 中,雖然 POD (一般舊資料) 物件配置和 COM 介面在各個版本之間保證不會有重大變更,但其他種類的物件配置 (例如,涉及繼承或樣板具現化的情節) 則可能會有變更。
為了避免發生難以偵測及診斷的執行階段錯誤,我們建議您絕不要以靜態方式連結至使用不同版本編譯器所編譯的二進位檔。此外,當您升級 EXE 或 DLL 專案時,請務必也要升級它所連結的程式庫。如果您是使用 CRT (C 執行階段) 或 STL (標準範本庫) 類型,則不要在使用不同版本編譯器所編譯的二進位檔 (包括 DLL) 之間傳遞它們。如需詳細資訊,請參閱跨 DLL 界限傳遞 CRT 物件時可能發生的錯誤。
我們還要建議您不要為不是 COM 介面或 POD 物件的物件撰寫依賴特定配置的程式碼。如果您撰寫了這種程式碼,則必須確定它在升級之後可以正確運作。如需詳細資訊,請參閱ABI 界限上的可攜性 (現代 C++)。
本文的其餘部分將說明 Visual Studio 2013 中的 Visual C++ 中的特定重大變更。
Visual C++ 編譯器
final 關鍵字現在會在先前可編譯的位置產生無法解析的符號錯誤:
struct S1 { virtual void f() = 0; }; struct S2 final : public S1 { virtual void f(); }; int main(S2 *p) { p->f(); }
在舊版中,由於呼叫是虛擬呼叫,因此不會發出錯誤,儘管如此,程式還是會在執行階段損毀。現在,由於已知類別為 final,因此會發出連結器錯誤。在這個範例中,為了修正錯誤,您會連結包含 S2::f 之定義的 obj。
當您使用命名空間中的 friend 函式時,必須在參考 friend 函式之前重新宣告該函式,否則會收到錯誤,因為編譯器現在遵循 ISO C++ Standard。例如,下面程式碼將不再進行編譯:
namespace NS { class C { void func(int); friend void func(C* const) {} }; void C::func(int) { NS::func(this); // error } }
若要更正這個程式碼,請宣告 friend 函式:
namespace NS { class C { void func(int); friend void func(C* const) {} }; void func(C* const); // conforming fix void C::func(int) { NS::func(this); } }
C++ Standard 不允許在類別中明確特製化。雖然 Visual C++ 在某些情況下允許這種做法,但是在像下列範例這樣的情況下,現在就會產生錯誤,因為編譯器不會將第二個函式視為第一個函式的特製化。
template <int N> class S { public: template void f(T& val); template <> void f(char val); }; template class S<1>;
若要更正這個程式碼,請修改第二個函式:
template <> void f(char& val);
Visual C++ 不再嘗試清楚區別下列範例中的兩個函式,而且現在會發出錯誤:
template<typename T> void Func(T* t = nullptr); template<typename T> void Func(...); int main() { Func<int>(); // error }
若要更正這個程式碼,請釐清呼叫:
template<typename T> void Func(T* t = nullptr); template<typename T> void Func(...); int main() { Func<int>(nullptr); // ok }
在編譯器符合 ISO C++11 之前,下列程式碼會進行編譯並導致 x 解析為 int 類型:
auto x = {0}; int y = x;
這個程式碼現在會將 x 解析為 std::initializer_list<int> 類型,而導致在下一行上嘗試將 x 指派給 int 類型時發生錯誤 (預設不會進行轉換)。 若要更正這個程式碼,請使用 int 來取代 auto:
int x = {0}; int y = x;
當右邊值的類型不符合要初始化之左邊值的類型時,不再允許彙總初始化,而且會發出錯誤,因為 C++11 ISO Standard 要求在不縮小轉換的情況下進行一致的初始化作業。在過去,如果可進行縮小轉換,就會發出 C4242 警告而非錯誤。
int i = 0; char c = {i}; // error
若要更正這個程式碼,請加入明確的縮小轉換:
int i = 0; char c = {static_cast<char>(i)};
不再允許下列初始化:
void *p = {{0}};
若要更正這個程式碼,請使用下列任一形式:
void *p = 0; // or void *p = {0};
名稱查閱已變更。 下列程式碼在 Visual Studio 2012 中的 Visual C++ 和 Visual Studio 2013 中的 Visual C++ 中會以不同方式解析:
enum class E1 {a}; enum class E2 {b}; int main() { typedef E2 E1; E1::b; }
在 Visual Studio 2012 中的 Visual C++ 中,E1 運算式中的 E1::b 會解析為全域範圍的 ::E1。在 Visual Studio 2013 中的 Visual C++ 中,E1 運算式中的 E1::b 會解析成 typedef E2 中的 main() 定義,並且具有 ::E2 類型。
物件配置已變更。 在 x64 上,類別的物件配置可能會和先前的版本不同。如果它具有虛擬函式,但沒有具有虛擬函式的基底類別,則編譯器的物件模型會在資料成員配置之後在虛擬函式表中插入指標。這表示該配置可能無法在所有情況下都是最佳。在舊版本中,有一個 x64 的最佳化設定會試著為您改善配置,不過,因為它在複雜程式碼情況下無法正確運作,所以在 Visual Studio 2013 中的 Visual C++ 中已經將它移除。例如,請參考這個程式碼:
__declspec(align(16)) struct S1 { }; struct S2 { virtual ~S2(); void *p; S1 s; };
在 Visual Studio 2013 中的 Visual C++中,在 x64 上 sizeof(S2) 的結果為 48,但是在舊版本中會判斷值為 32。若要在 x64 的 Visual Studio 2013 中的 Visual C++ 中讓此判斷值為 32,請加入具有虛擬函式的虛擬基底類別:
__declspec(align(16)) struct S1 { }; struct dummy { virtual ~dummy() {} }; struct S2 : public dummy { virtual ~S2(); void *p; S1 s; };
若要在程式碼中尋找舊版本嘗試進行最佳化的地方,請搭配 /W3 編譯器選項來使用該版本的編譯器並開啟警告 4370。例如:
#pragma warning(default:4370) __declspec(align(16)) struct S1 { }; struct S2 { virtual ~S2(); void *p; S1 s; };
在 Visual Studio 2013 中的 Visual C++ 之前的 Visual C++ 編譯器中,此程式碼會輸出下面訊息:
warning C4370: 'S2' : layout of class has changed from a previous version of the compiler due to better packing
在所有 Visual C++ 版本中,x86 編譯器都具有相同的配置不佳問題。例如,如果這個程式碼是為 x86 而編譯:
struct S { virtual ~S(); int i; double d; };
sizeof(S) 的結果會是 24。不過,如果您使用剛才提到的 x64 解決方法,則此結果可以減少為 16:
struct dummy { virtual ~dummy() {} }; struct S : public dummy { virtual ~S(); int i; double d; };
Visual C++ 程式庫
標準樣板程式庫
為了啟用新的最佳化和偵錯檢查,Visual Studio 所實作的 C++ 標準程式庫是刻意中斷各個版本之間的二進位碼相容性 (Binary Compatibility)。因此,使用 C++ 標準程式庫時,使用不同版本所編譯的目的檔和靜態程式庫不可以混合在一個二進位檔 (EXE 或 DLL) 中,也不可以在使用不同版本所編譯的二進位檔之間傳遞 C++ 標準程式庫物件。這類混合會發出有關 _MSC_VER 不符的連結器錯誤 (_MSC_VER 是包含編譯器主要版本 (例如 Visual Studio 2013 中的 1800 for Visual C++) 的巨集。) 此檢查無法偵測 DLL 混合,且無法偵測包含 Visual C++ 2008 及較舊版本的混合。
Visual Studio 2013 中的 Visual C++ 可偵測 _ITERATOR_DEBUG_LEVEL 中不相符的情況 (這是在 Visual C++ 2010 中所實作) 以及 RuntimeLibrary 不相符的情況。當編譯器選項 /MT (靜態發行)、/MTd (靜態偵錯)、/MD (動態發行) 和 /MDd (動態偵錯) 混合時就會發生這些情況。如需詳細資訊,請參閱 Visual C++ 2012 的重大變更。
如果您的程式碼認可先前版本的模擬別名範本,則需要加以變更。例如,現在您必須改成 allocator_traits<A>::rebind_alloc<U>::other,而不是 allocator_traits<A>::rebind_alloc<U>。雖然已不再需要 ratio_add<R1, R2>::type,而且現在建議您使用 ratio_add<R1, R2>,但前者還是會進行編譯,因為 ratio<N, D> 必須有縮減一定比例的「類型」typedef (如果已經縮減,則會是相同類型)。
在呼叫 #include <algorithm> 或 std::min() 時,您必須使用 std::max()。
如果您的現有程式碼使用舊版的模擬範圍列舉 (包裝在命名空間中的傳統不限範圍列舉),則必須加以變更。例如,如果您原本參考 std::future_status::future_status 類型,則現在必須改為 std::future_status。不過,大部分的程式碼不會受影響,例如 std::future_status::ready 仍會編譯。
explicit operator bool() 比 operator unspecified-bool-type() 更嚴格。explicit operator bool() 允許明確轉換為 bool (例如,假設 shared_ptr<X> sp,static_cast<bool>(sp) 和 bool b(sp) 都有效),以及轉換為 bool 的可進行布林值測試之「內容轉換」(例如 if (sp)、!sp、sp && whatever)。不過,explicit operator bool() 會禁止隱含轉換成 bool,因此您不能使用 bool b = sp,且假設傳回類型為 bool,則您不能使用 return sp。
現在已實作真正的 variadic 樣板,因此 _VARIADIC_MAX 和相關的巨集不會有任何作用。 如果您仍然定義 _VARIADIC_MAX,則會直接將它忽略。 如果您認可我們的巨集機制主要在於以任何其他方式支援模擬的 variadic 樣板,那麼您必須變更程式碼。
除了一般關鍵字之外,STL 標頭現在禁止將有內容相關性的關鍵字 "override" 和 "final" 巨集化。
reference_wrapper/ref()/cref() 現在禁止繫結至暫存物件。
<random> 現在會嚴格強制進行其編譯時期前置條件。
各種 STL 類型特性都有前置條件「T 應為完整類型」。雖然編譯器現在會更嚴格地強制執行這點,但是並非所有情況下都會強制執行 (由於 STL 前置條件違規會觸發未定義的行為,因此 Standard 不保證能強制執行)。
STL 不支援 /clr:oldSyntax。
common_type<> 的 C++11 規格發生未預期且不想要的結果,尤其是它會使 common_type<int, int>::type 傳回 int&&。因此,Visual C++ 會實作程式庫工作小組問題 2141 的建議解決方法,它會讓 common_type<int, int>::type 傳回 int。
這項變更的副作用就是識別案例不再適用 (common_type<T> 不一定會產生 T 類型)。這個結果符合建議的解決方法,不過它會破壞依賴之前行為的任何程式碼。
如果您需要識別類型特性,請不要使用 std::identity 中定義的非標準 <type_traits>,因為它不適用於 <void>。請改為依照您的需求,實作自己的識別類型特性。以下為範例:
template <typename T> struct Identity { typedef T type; };
MFC 和 ATL
MFC MBCS 程式庫不再包含於 Visual Studio 之中,因為 Unicode 相當普遍,所以 MBCS 的使用率大幅降低。這項變更也讓 MFC 與 Windows SDK 本身更為相符,因為許多新的控制項和訊息都限用 Unicode。不過,如果您必須繼續使用 MFC MBCS 程式庫,可以從 MSDN 下載中心進行下載。Visual C++ 可轉散發套件仍然包含這個程式庫。
MFC 功能區的存取範圍已變更: 現在是階層架構,而非單層架構。 您仍然可以藉由呼叫 CRibbonBar::EnableSingleLevelAccessibilityMode() 使用舊有行為。
CDatabase::GetConnect 方法已移除: 為增強安全性,連接字串現在會加密儲存,並且只在需要時解密,而無法做為純文字傳回。 使用 CDatabase::Dump 方法即可取得字串。
CWnd::OnPowerBroadcast 的簽章已變更: 這個訊息處理常式的簽章已變更為接受 LPARAM 做為第二個參數。
簽章已變更為可容納訊息處理常式: 下列函式的參數清單已變更為使用新加入的 ON_WM_* 訊息處理常式:
CWnd::OnDisplayChange 已變更為 (UINT, int, int) 而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_DISPLAYCHANGE 巨集。
CFrameWnd::OnDDEInitiate 已變更為 (CWnd*, UINT, UNIT) 而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_DDE_INITIATE 巨集。
CFrameWnd::OnDDEExecute 已變更為 (CWnd*, HANDLE) 而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_DDE_EXECUTE 巨集。
CFrameWnd::OnDDETerminate 已變更為 (CWnd*),它是參數而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_DDE_TERMINATE 巨集。
CMFCMaskedEdit::OnCut 已變更為無參數,而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_CUT 巨集。
CMFCMaskedEdit::OnClear 已變更為無參數,而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_CLEAR 巨集。
CMFCMaskedEdit::OnPaste 已變更為無參數,而非 (WPARAM, LPARAM),因此可以在訊息對應中使用新的 ON_WM_PASTE 巨集。
MFC 標頭檔中的 #ifdef 已移除。 MFC 標頭檔中與不支援的 Windows 版本 (#ifdef) 相關的許多 WINVER < 0x0501 已移除。
ATL DLL (atl120.dll) 已移除: 現在提供的 ATL 為標頭和靜態程式庫 (atls.lib)。
Atlsd.lib、atlsn.lib 和 atlsnd.lib 已移除: Atls.lib 不再具有字元集相依性或偵錯/發行專屬的程式碼。由於對於 Unicode/ANSI 和偵錯/發行,它的運作方式相同,因此只需要一個版本的程式庫。
移除 ATL DLL 時會同時移除 ATL/MFC 追蹤工具並簡化追蹤機制。CTraceCategory 建構函式現在會接受一個參數 (分類名稱),而 TRACE 巨集會呼叫 CRT 偵錯回報函式。