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\n"); }
        ~S(){ printf("Destroying S\n"); }
    };
    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.\n");
        gets_s(s);
        return 0;
    }

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

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

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

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

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

    #include <stdio.h>
    
    struct S {
        void Create() { printf("Creating S.\n"); }
        void Destroy() { printf("Destroying S\n"); }
    };
    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.\n");
    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”的参数
    ..\t331.cpp(14):备注:请参阅“D”的声明
    ..\t331.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++ 或库编译的)。

    Visual C++ 中的重大更改警告

    在这种情况下,应通过将 legacy_stdio_definitions.lib 添加到链接器输入(如上文所述)来解决该错误。

    请尝试使用下面的命令行来查看在库中定义的符号。

    dumpbin.exe /LINKERMEMBER somelibrary.lib
  • gets 和 _getws

    _getws_s。

  • _cgets 和 _cgetws

    _cgetws_s。

  • 无穷大和非数字 (NaN) 格式设置

    在早期版本中,可以使用 Visual C++ 特定的 sentinel 字符串集进行无穷大和 NaN 格式设置。

    • 无穷大:1.#INF

    • 静默 NaN:1.#QNAN

    • 信号 NaN:1.#SNAN

    • 不定 NaN:1.#IND

    新字符串如下所示:

    • 无穷大:inf

    • 静默 NaN:nan

    • 信号 NaN:nan(snan)

    • 不定 NaN:nan(ind)

    如果使用了大写格式说明符(%F 而不是 %f),则字符串将按要求以大写字母形式(INF 而不是 inf)打印。

    scanf 函数以便分析这些新的字符串,因此这些字符串会通过 printf 和 scanf 往返。

  • 浮点格式设置和分析

    strtod 这样的函数。

    作为改进的一个例子;打印两个中指数较大的一个时,请考虑结果:

    旧版本:1208925819614629200000000
    新版本:1208925819614629174706176

    新版本的结果始终比旧版本的结果更准备。

  • 十六进制和无穷大/NaN 浮点分析

    浮点分析算法现在将分析十六进制浮点字符串(例如,那些由 %a 和 %A printf 格式说明符生成的字符串)和由 printf 函数生成的所有无穷大和 NaN 字符串(如上文所述)。 

  • %A 和 %a 零填充

    已解决此问题。

  • %A 和 %a 精度

    为了符合 C 标准,现在默认精度为 13。

    精度规范。

  • %F 说明符

    它在功能上等效于 %f 格式说明符,但使用大写字母形式进行格式设置的无穷大和 Nan 除外。

    如果遇到 %F,现在则将其视为 %F 格式说明符;如果遇到 %N,现在则将其视为无效的参数。

  • 指数格式设置

    这是错误的:根据 C 要求,如果可使用一个或两个数字表示指数,则仅打印两个数字。

    已将默认行为更改为符合标准的指数打印模式。

  • 格式字符串验证

    现在所有无效格式字符串都被视为无效的参数。

  • fopen 模式字符串验证

    现在可检测无效的模式字符串并将其视为无效的参数。

  • _O_U8TEXT 模式

    在早期版本的库中,它将报告正在 _O_WTEXT 中打开的此类流。

    如果你的应用程序不支持 UTF_8,请考虑为此越来越常见的编码添加支持。

  • snprintf 和 vsnprintf

    vsnprintf 在包括 <stdio.h> 之前定义为宏,则现在编译将因出错而失败,该错误指示定义了宏的位置。

    通常情况下,解决此问题的方法是删除用户代码中 snprintf 或 vsnprintf 的任何声明。

  • tmpnam 生成可用文件名

    这些函数现在在临时目录中生成可用的文件名路径。

  • 文件封装

    作为此操作的一部分,<stdio.h> 中所定义的文件现在是不透明类型且无法从 CRT 自身外部访问其成员。

  • _outp 和 _inp

    _inpd。

<stdlib.h>、<malloc.h> 和 <sys/stat.h>
  • strtof 和 wcstof

    (请注意此错误只特定于这两个函数;strtod、wcstod、strtold 和 wcstold 函数不受影响。) 这是运行时重大更改。

  • 对齐的分配函数

    这是运行时重大更改。

  • 堆函数

    这些函数已不起作用,因为 CRT 已更新为使用 Windows 堆。

  • smallheap

    链接选项。

<string.h>
  • wcstok

    在早期版本的库中,此函数的签名为:

    wchar_t* wcstok(wchar_t*, wchar_t const*)

    该函数现在具有签名 wchar_t* wcstok(wchar_t*、wchar_t const*、wchar_t**),并要求调用方将上下文作为第三个参数传递给函数。

    在 C 代码中,你可能会定义 _CRT_NON_CONFORMING_WCSTOK 以使 _wcstok 用于替换 wcstok。

<time.h>
  • clock

    QueryPerformanceCounter 重新实现了 clock 函数,现在它是单一的。

  • fstat 和 _utime

    在 Visual Studio 2013 之前的版本中,所有这些函数错误调整标准时时间,就像处于夏时制时间内一样。

    现在已修复 fstat 和 _utime 系列函数,因此所有这些函数现在可正确且一致地处理夏时制。

  • asctime

    已解决此问题。

  • strftime 和 wcsftime

    此外,分析但忽略了 E 和 O 修饰符。

    已解决此问题。

  • timespec 和 TIME_UTC

    这对于在任一这些方面具有冲突定义的代码而言,是一项重大更改。

  • CLOCKS_PER_SEC

    现在,CLOCKS_PER_SEC 宏根据 C 语言要求扩展为整数类型 clock_t。

标准模板库

(_MSC_VER 是包含编译器主版本的宏,例如,Visual Studio 2013 的 1800。) 此检查无法检测 DLL 混合,也无法检测涉及 Visual C++ 2008 或早期版本的混合。

  • STL 包含文件

    第二,<tuple> 现在用于声明 std::array 但不包括所有 <array>,这可能中断代码通过以下代码构造的组合:代码具有名为“数组”的变量,你具有 using 指令“using namespace std;” 和你包括含有 <tuple> 的 STL 标头(如 <functional>),其现在用于声明 std::array。

  • steady_clock

    因此,在 Visual C++ 中,steady_clock::time_point 现在是 chrono::time_point<steady_clock> 的 typedef;但是,其他实现不一定是这种情况。

  • 分配器和 const

    现在,我们要求分配器进行相等/不等比较,以接受两端上的 const 参数。  如果你的分配器如下定义这些运算符:

    bool operator==(const MyAlloc& other)

    你应更新这些以将它们声明为 const 成员。

    bool operator==(const MyAlloc& other) const
  • const 元素

    在当前版本中,此类容器无法编译。

  • std::allocator::deallocate

    在运行时,为 n 传递不同于标准要求的参数的代码可能会崩溃。

  • hash_map 和 hash_set

    请改用 unordered_map 和 unordered_set。

  • 比较运算符和 operator()

    现在比较运算符类声明中的以下代码无法进行编译:

    bool operator()(const X& a, const X& b)

    若要解决此错误,请将函数声明更改为:

    bool operator()(const X& a, const X& b) const
  • 类型特征

    下表显示了旧名称和新名称。

    旧名称

    新名称

    add_reference

    add_lvalue_reference

    has_default_constructor

    is_default_constructible

    has_copy_constructor

    is_copy_constructible

    has_move_constructor

    is_move_constructible

    has_nothrow_constructor

    is_nothrow_default_constructible

    has_nothrow_default_constructor

    is_nothrow_default_constructible

    has_nothrow_copy

    is_nothrow_copy_constructible

    has_nothrow_copy_constructor

    is_nothrow_copy_constructible

    has_nothrow_move_constructor

    is_nothrow_move_constructible

    has_nothrow_assign

    is_nothrow_copy_assignable

    has_nothrow_copy_assign

    is_nothrow_copy_assignable

    has_nothrow_move_assign

    is_nothrow_move_assignable

    has_trivial_constructor

    is_trivially_default_constructible

    has_trivial_default_constructor

    is_trivially_default_constructible

    has_trivial_copy

    is_trivially_copy_constructible

    has_trivial_move_constructor

    is_trivially_move_constructible

    has_trivial_assign

    is_trivially_copy_assignable

    has_trivial_move_assign

    is_trivially_move_assignable

    has_trivial_destructor

    is_trivially_destructible

  • launch::any 和 launch::sync 策略

    launch 枚举。

MFC 和 ATL
  • 可从控制面板、程序和功能,或从安装媒体重新运行 Visual Studio 安装程序。

    Visual C++ 可再发行组件包仍包含此库。

并发运行时
  1. 与 concurrency::Context::Yield 冲突的 Windows.h 中的 Yield 宏

    若要解决与 Yield 的冲突,可以改为更新代码以调用 YieldExecution 函数,或在调用站点用括号将 Yield 函数名括起来,如下例所示:

    (concurrency::Context::Yield)();

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-10-21
  • 2021-12-27
  • 2021-04-16
  • 2022-02-12
  • 2021-11-15
  • 2021-07-14
猜你喜欢
  • 2021-09-17
  • 2022-12-23
  • 2021-12-25
  • 2021-11-26
  • 2021-06-28
相关资源
相似解决方案