Visual C++ 中的重大更改

https://technet.microsoft.com/zh-cn/learning/bb531344.aspx
 

新版本中会引起这类问题的更改称为重大更改,通常,修改 C++ 语言标准、函数签名或内存中的对象布局时需要进行这种更改。

跨 DLL 边界传递 CRT 对象时可能的错误。

ABI 边界处的可移植性(现代 C++)。

术语“旧行为”和“之前”指 Visual Studio 2013 和早期版本。

  1. 编译器的重大更改

  2. b38385a9-a483-4de9-99a6-797488bc5110#BK_CRT

  3. b38385a9-a483-4de9-99a6-797488bc5110#BK_STL

  4. b38385a9-a483-4de9-99a6-797488bc5110#BK_MFC

Visual C++ 编译器
  • /Zc:forScope- 选项

    /Zc:forScope- 已弃用,并且将在将来版本中删除。

    Command line warning  D9035: option 'Zc:forScope-' has been deprecated and will be removed in a future release

    如果你确实关心编写可移植且符合标准的代码,则应重写代码,以便通过将此类变量的声明移到循环以外的点使其符合标准。

    // zc_forScope.cpp
    // compile with: /Zc:forScope- /Za
    // C2065 expected
    int main() {
       // Uncomment the following line to resolve.
       // int i;
       for (int i =0; i < 1; i++)
          ;
       i = 20;   // i has already gone out of scope under /Za
    }
  • /Zg 编译器选项

    此此编译器选项已被弃用。

  • VSTest.Console.exe 命令行选项。

  • 可变关键字

    根据标准,可变说明符仅可应用于类数据成员的名称,不能应用于声明为 const 或 static 的名称,也不能应用于引用成员。

    例如,考虑以下代码:

    struct S {
        mutable int &r;
    };

    早期版本的 Visual C++ 编译器接受此代码,但现在编译器则报告以下错误:

    错误 C2071: 'S::r':非法存储类

    若要修复此错误,只需删除冗余的可变关键字。

  • char_16_t 和 char32_t

    用户和库作者通常会将 char16_t 和 char32_t 分别定义为 uint16_t 和 uint32_t 的别名。

    #include <cstdint>
    
    typedef uint16_t char16_t; //C2628
    typedef uint32_t char32_t; //C2628
    
    int main(int argc, char* argv[])
    {
    uint16_t x = 1; uint32_t y = 2;
    char16_t a = x; 
    char32_t b = y; 
    return 0;
    }

    若要更新你的代码,请删除 typedef 声明,并重命名与这些名称发生冲突的任何其他标识符。

  • 非类型模板参数

    例如,在早期版本的 Visual C++ 中正确编译的以下代码。

    struct S1
    {
    void f(int);
    void f(int, int);
    };
    
    struct S2
    {
    template <class C, void (C::*Function)(int) const> void f() {}
    };
    
    void f()
    {
    S2 s2;
    s2.f<S1, &S1::f>();
    }
    

    当前编译器可以准确报告错误,因为模板参数类型不匹配模板参数(该参数是指向 const 成员的指针,但函数为非 const):

    错误 C2893:未能特殊化函数模板“void S2::f(void)”
    备注:使用以下模板参数:
    备注:“C=S1”
    备注:“Function=S1::f”

    若要在代码中修复此错误,请确保你使用的模板参数类型匹配模板参数声明的类型。

  • __declspec(align)

    以前会始终忽略此项,但现在会产生编译器错误。

    error C3323: 'alignas' and '__declspec(align)' are not allowed on function declarations

    因为它不起作用,将其删除不会更改任何内容。

  • 异常处理

    在 Visual Studio 2013 中的 Visual C++ 中编译的以下代码却不能在 Visual Studio 2015 中的 Visual C++ 中进行编译:

    struct S {
    public:
        S();
    private:
        S(const S &);
    };
    
    int main()
    {
        throw S(); // error
    }
    

    当复制构造函数为声明的 explicit 时,这同样适用。

    struct S {
        S();
        explicit S(const S &);
    };
    
    int main()
    {
        throw S(); // error
    }
    

    若要更新你的代码,请确保异常对象的复制构造函数是公用的且未标记为 explicit

    在 Visual Studio 2013 中的 Visual C++ 中编译的以下代码却不能在 Visual Studio 2015 中的 Visual C++ 中进行编译:

    struct B {
    public:
        B();
    private:
        B(const B &);
    };
    
    struct D : public B {
    };
    
    int main()
    {
        try
        {
        }
        catch (D d) // error
        {
        }
    }
    

    可以通过将 catch 的参数类型更改为引用来解决此问题。

    catch(D& d)
    {
    }
  • 后面是宏的字符串文本

    例如,在早期的编译器中,成功编译了以下代码:

    在 Visual Studio 2015 中的 Visual C++ 中,编译器将此视为用户定义的文字,但由于没有定义匹配的用户定义的 _x 文本,它将报告错误。

    若要解决此问题,请在字符串文本和宏之间添加一个空格。

  • 相邻字符串文本

    例如,必须更改以下代码:

    只需在两个字符串之间添加空间。

  • placement new 和 placement delete

    重大更改为,如果你之前使用的是具有相同签名的运算符 delete(以与 placement new 运算符对应),你将收到编译器错误(C2956,在使用 placement new 的点位置出现,因为在代码中的该位置,编译器会尝试标识适当匹配的 delete 运算符)。

    标准要求为,如果使用 placement new 查找相应的 delete 函数和常用释放函数,则程序会出现格式错误。

    例如,假设你的代码同时定义了 placement new 和 placement delete:

    void * operator new(std::size_t, std::size_t);
    void operator delete(void*, std::size_t) noexcept; 
    

    较好的解决办法就是使用如下的枚举类型:

    enum class my_type : size_t {};
    

    你无需为此使用枚举;具有 size_t 成员的类类型也将起作用。

    如果你的代码使用 placement new 实现内存池,其中位置参数是分配或删除的对象的大小,则调整了大小的释放功能可能适合替换你自定义的内存池代码,且你可以去掉位置函数,仅使用自己两个参数的 delete 运算符(而不是位置参数)。

    如果使用此选项,则不存在两个参数的 delete 函数,并且也不会导致与 placement delete 运算符发生冲突。

  • 联合数据成员

    以下代码在 Visual Studio 2013 中的 Visual C++中成功编译,但在 Visual Studio 2015 中的 Visual C++ 中产生错误。

    union U1 {
        const int i;
    };
    union U2 {
       int &i;
    };
    union U3 {
        struct {int &i;};
    };

    前面的代码产生以下错误:

    test.cpp(67):错误 C2625:U2::i:非法的联合成员;类型“int &”为引用类型
    test.cpp(70):错误 C2625:U3::i:非法的联合成员;类型“int &”为引用类型

    根据值的大小,它还可能更改联合的大小。

  • 这些在 Visual Studio 2015 中的 Visual C++ 中已删除。

    struct S {
      S();
     };
    
     union {
      struct {
       S s;
      };
     } u; // C2280

    前面的代码在 Visual Studio 2015 中的 Visual C++ 中生成以下错误:

    error C2280: '<unnamed-type-u>::<unnamed-type-u>(void)': attempting to reference a deleted function
    note: compiler has generated '<unnamed-type-u>::<unnamed-type-u>' here

    若要解决此问题,请提供你对构造函数和/或析构函数的定义。

    struct S {
    // Provide a default constructor by adding an empty function body.
    S() {} 
    };
    
    union {
    struct {
    S s;
    };
    } u;
  • 具有匿名结构的联合

    请考虑以下代码,其中联合 U 包含一个匿名结构,此匿名结构包含的成员是一个具有析构函数的命名结构 S。

    #include <stdio.h>
    struct S {
        S() { printf("Creating S
    "); }
        ~S(){ printf("Destroying S
    "); }
    };
    union U {
        struct {
        S s;
    };
        U() {}
        ~U(){}
    };
    
    void f()
    {
        U u;
        // Destructor implicitly called here.
    }
    
    int main()
    {
        f();
    
        char s[1024];
        printf("Press any key.
    ");
        gets_s(s);
        return 0;
    }

    编译器会对关于此行为的更改发出警告。

    警告 C4587:U::s:行为更改:不再隐式调用构造函数
    警告 C4588:U::s:行为更改:不再隐式调用析构函数

    无论编译器版本为何,非匿名结构的运行时行为都是相同的。

    #include <stdio.h>
    
    struct S {
        S() { printf("Creating S.
    "); }
        ~S() { printf("Destroying S
    "); }
    };
    union U {
        struct {
            S s;
        } namedStruct;
        U() {}
        ~U() {}
    };
    
    void f()
    {
        U u;
    }
    
    int main()
    {
        f();
    
        char s[1024];
        printf("Press any key.
    ");
        gets_s(s);
        return 0;
    }

    或者,尝试将构造函数和析构函数代码移到新的函数中,并从联合的构造函数和析构函数添加对这些函数的调用。

    #include <stdio.h>
    
    struct S {
        void Create() { printf("Creating S.
    "); }
        void Destroy() { printf("Destroying S
    "); }
    };
    union U {
        struct {
            S s;
        };
        U() { s.Create();  }
        ~U() { s.Destroy(); }
    };
    
    
    void f()
    {
        U u;
    }
    
    int main()
    {
        f();
    
    char s[1024];
    printf("Press any key.
    ");
    gets_s(s);
    return 0;
    }
  • 模板解析

    这些无效的实例化通常不会导致编译器错误,这被称为 SFINAE(替换失败不是错误)原则。

    例如,考虑以下代码:

    #include <type_traits>
    
    template<typename T>
    struct S
    {
    S() = default;
    S(const S&);
    S(S&&);
    
    template<typename U, typename = typename std::enable_if<std::is_base_of<T, U>::value>::type>
    S(S<U>&&);
    };
    
    struct D;
    
    void f1()
    {
    S<D> s1;
        S<D> s2(s1);
    }
    
    struct B
    {
    };
    
    struct D : public B
    {
    };
    
    void f2()
    {
    S<D> s1;
        S<D> s2(s1);
    }
    
    

    如果使用当前编译器进行编译,将得到以下错误:

    type_traits(1110):错误 C2139:“D”:未定义的类不允许作为编译器内部类型特征“__is_base_of”的参数
    ..	331.cpp(14):备注:请参阅“D”的声明
    ..	331.cpp(10):备注:请参阅对正在编译的类模板实例化“std::is_base_of<T,U>”的引用
            替换为
            [
                T=D,
                U=D
            ]

    这是因为在第一次调用 is_base_of 时,尚未定义类“D”。

    如果定义位于标头文件中,请检查标头文件的 include 语句的顺序,以确保在使用有问题的模板之前,对任何类定义进行了编译。

  • 复制构造函数

    在 Dev14 中,此隐式生成的复制构造函数也标记为“= delete”。

C 运行库 (CRT)
 
常规更改
  • 重构的二进制文件

    CRT 库功能。

<locale.h>
  • localeconv

    在早期版本的库中,此函数将返回全局区域设置(而不是线程的区域设置)的 lconv 数据。

    如果使用每个线程区域设置,应该检查 localeconv 的使用以查看你的代码是否假定返回的 lconv 数据代表全局区域设置,并相应地对其进行修改。

<math.h>
  • 数学库函数的 C++ 重载

    现在,已从 <math.h> 中删除了所有 C++ 重载,现在仅包含在 <cmath> 中。

    下表列出了移动的函数。

    移动的函数:

    1. 双精度型 abs(double) 和浮点型 abs(float)

    2. 双精度型 pow(double, int)、浮点型 pow(float, float)、浮点型 pow(float, int)、长双精度型 pow(long double, long double)、长双精度型 pow(long double, int)

    3. 浮点型和长双精度型版本的浮点函数 acos、acosh、asin、asinh、atan、atanh、atan2、cbrt、ceil、copysign、cos、cosh、erf、erfc、exp、exp2、expm1、fabs、fdim、floor、fma、fmax、fmin、fmod、frexp、hypot、ilogb、ldexp、lgamma、llrint、llround、log、log10、log1p、log2、lrint、lround、modf、nearbyint、nextafter、nexttoward、remainder、remquo、rint、round、scalbln、scalbn、sin、sinh、sqrt、tan、tanh、tgamma、trunc

    这将产生错误:

    警告 C4244:“参数”:从“float”转换为“int”,可能丢失数据

    此警告的解决方法是将对 abs 的调用替换为浮点版本的 abs(例如双精度型参数的 fabs 或浮点型参数的 fabsf)或包含 cmath 标头并继续使用 abs。

  • 浮点一致性

    C11 标准的附录 F。

    这些更改不会导致编译时错误,但可能会根据标准使程序以不同的方式更准确地运行。

  • FLT_ROUNDS

    FLT_ROUNDS 宏现在是动态的,并正确反映当前的舍入模式。

<new> 和 <new.h>
  • new 和 delete

    这些运算符函数现在始终以静态方式链接到二进制文件,即使是使用运行时库 DLL 时也是如此。

    请注意,/clr:pure 在Visual Studio 2015 RC 中已被弃用,并且可能在未来版本中删除。

<process.h>
  • _beginthread 和 _beginthreadex

    这有助于确保线程在完成运行之后才卸载模块。

<stdarg.h>
  • va_start 和引用类型

    C++ 标准禁止引用类型的参数。

<stdio.h> 和 <conio.h>
  • Printf 和 scanf 系列函数现在采用内联方式进行定义。

    如果可能,应更新代码以包括 CRT 标头(即,添加 #include <stdio.h>)和内联函数,但如果不想修改代码以包括这些标头文件,则可以选择将其他库添加到链接器输入 (legacy_stdio_definitions.lib)。

    若要将此库添加到 IDE 中的链接器输入,请打开项目节点的上下文菜单,选择“属性”,然后在“项目属性”对话框中选择“链接器”,编辑“链接器输入”以将 legacy_stdio_definitions.lib 添加到用分号隔开的列表。

    如果库是第三方库并且第三方库的源不可用,则应请求来自第三方更新后的二进制文件,或者将你对此库的用法封装到单独的 DLL(使用旧版 Visual C++ 或库编译的)。