Visual C++ 中的重大更改

当你升级到 Visual C++ 编译器的新版本后,可能会在之前编译并正常运行的代码中遇到编译和/或运行时错误。新版本中会引起这类问题的更改称为重大更改,通常,修改 C++ 语言标准、函数签名或内存中的对象布局时需要进行这种更改。

在 Visual C++ 中,虽然 POD(纯旧数据)对象布局和 COM 接口保证不会随版本而发生重大更改,但其他类型的对象布局(例如,在涉及继承或模板实例化的情况下)则可能会发生变化。

若要避免难以检测和诊断的运行时错误,我们建议你永远不静态链接到使用不同编译器版本编译的二进制文件。此外,当你升级 EXE 或 DLL 项目时,请确保升级它所链接的库。如果使用 CRT(C 运行时库)或 STL(标准模板库)类型,请勿在使用不同编译器版本编译的二进制文件(包括 DLL)之间传递这些类型。有关详细信息,请参阅跨 DLL 边界传递 CRT 对象时可能的错误

我们进一步建议,你在编写代码时永远不依赖除 COM 接口或 POD 对象以外的特定对象布局。如果确实要编写此类代码,则必须在升级后确保其正常运行。有关详细信息,请参阅ABI 边界处的可移植性(现代 C++)

本文的其余部分将介绍 Visual Studio 2013 中的 Visual C++ 中的具体重大更改。

Visual C++ 编译器

  • 最终关键字现在会在它以前编译过的位置生成未解析的符号错误:

    struct S1 {
        virtual void f() = 0;
    };
    
    struct S2 final : public S1 {
        virtual void f();
    };
    
    int main(S2 *p)
    {
        p->f();
    }
    

    在早期版本中,由于调用为虚调用,因此未发出错误;但程序可能在运行时崩溃。现在,由于类已知为最终的类,因此将发出链接器错误。在此示例中,若要修复错误,请针对包含 S2::f 定义的对象进行链接。

  • 你在使用命名空间中的友元函数时,必须先重新声明该友元函数,然后再对其进行引用,否则你将收到错误,因为编译器现在遵循 ISO C++ 标准。例如,此代码不再编译:

    namespace NS {
        class C {
            void func(int);
            friend void func(C* const) {}
        };
    
        void C::func(int) { 
            NS::func(this);  // error
        }
    }
    

    若要更正此代码,请声明友元函数:

    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++ 标准不允许类中存在显式专用化。虽然 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; 
    
  • 当右侧值的类型与要初始化的左侧值的类型不匹配时,不再允许聚合初始化,并且将发出错误,原因是 ISO C++11 标准要求统一初始化,以便在不进行收缩转换的情况下正常运行。之前,如果收缩转换可用,则会发出 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++ 中,sizeof(S2) 在 x64 上的结果为 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++ 库

标准模板库

为了实现新的优化和调试检查,C++ 标准库的 Visual Studio 实现特意破坏了连续两个版本之间的二进制兼容性。因此,在使用 C++ 标准库时,使用不同版本编译的对象文件和静态库不能混合在同一二进制文件(EXE 或 DLL)中,并且不能在使用不同版本编译的二进制文件之间传递 C++ 标准库对象。这样混合会发出关于 _MSC_VER 不匹配的链接器错误。(_MSC_VER 是包含编译器主版本的宏,例如,Visual Studio 2013 中的 Visual C++ 为 1800。) 此检查无法检测 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> 需要具有一个“type”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_MAX 和相关宏无效。 如果你仍在定义 _VARIADIC_MAX,请将其忽略。 如果确认了旨在以任何其他方式支持模拟的可变参数模板的宏机制,则必须更改代码。

  • 除普通关键字以外,STL 标头现在禁止宏化区分上下文的关键字“override”和“final”。

  • reference_wrapper/ref()/cref() 现在禁止绑定到临时对象。

  • <random> 现在严格强制实施其编译时间的前置条件。

  • 不同的 STL 类型特征共有的前置条件是“T 应为完整类型”。虽然编译器更严格地强制此功能,但不会在所有情形中进行强制。(由于 STL 前置条件违反了触发器未定义的行为,因此无法保证能执行此标准。)

  • 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

  • Visual Studio 中不再包括 MFC MBCS 库,因为 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。 与 Windows (#ifdef) 的不受支持的版本相关的 MFC 标头文件中的许多 WINVER < 0x0501 已移除。

  • ATL DLL (atl120.dll) 已移除。 ATL 现在作为标头和静态库 (atls.lib) 提供。

  • Atlsd.lib、atlsn.lib 和 atlsnd.lib 已移除。 Atls.lib 不再具有字符集依赖项或专用于调试/发布的代码。由于它与 Unicode/ANSI 和调试/发布的工作原理相同,因此,只需要库的一个版本。

  • ATL/MFC 跟踪工具已被移除(连同 ATL DLL),并且跟踪机制已得到简化。CTraceCategory 构造函数现在采用了一个参数(类别名称),TRACE 宏调用了 CRT 调试报告函数。

请参见

其他资源

Visual Studio 2013 Visual C++ 入门