Visual C++ 新增功能(2003 - 2015)Visual C++ What's New 2003 through 2015

注意:有关 Visual Studio 2017 的信息,请参阅 Visual Studio 2017 中 Visual C++ 的新增功能Visual Studio 2017 中 Visual C++ 的符合性改进Note For information about Visual Studio 2017, see What's new for Visual C++ in Visual Studio 2017 and Conformance Improvements in Visual C++ in Visual Studio 2017.

在 Visual C++ 2015 及更高版本中,对编译器符合性的持续改进有时会改变编译器理解现有源代码的方式。In Visual C++ 2015 and later, ongoing improvements to compiler conformance can sometimes change how the compiler understands your existing source code. 发生这种情况时,可能会在生成过程中遇到新的或不同的错误,甚至以前生成且似乎运行正常的代码也可能出现行为差异。When this happens, you might encounter new or different errors during your build, or even behavioral differences in code that previously built and seemed to run correctly.

幸运的是,这些差异对大部分源代码没有影响或影响极小,而且需要更改源代码或进行其他更改以解决这些差异时,修补程序通常小型且简单。Fortunately, these differences have little or no impact on most of your source code and when source code or other changes are needed to address these differences, fixes are usually small and straight-forward. 我们列出了以前可接受、现在可能需要更改的许多源代码示例(之前)及其修补程序(之后)。We've included many examples of previously-acceptable source code that might need to be changed (before) and the fixes to correct them (after).

虽然这些差异可能会影响源代码或其他生成项目,但其不会影响 Visual C++ 版本更新之间的二进制文件兼容性。Although these differences can affect your source code or other build artifacts, they don't affect binary compatibility between updates to Visual C++ versions. 重大更改是严重性较高的更改,可能会影响二进制文件兼容性,但此类二进制文件兼容性中断问题仅发生在 Visual C++ 的主版本之间。A more-severe kind of change, the breaking change can affect binary compatibility, but these kinds of binary compatibility breaks only occur between major versions of Visual C++. 例如,在 Visual C ++ 2013 和 Visual C ++ 2015 之间。For example, between Visual C++ 2013 and Visual C++ 2015. 有关 Visual C++ 2013 和 Visual C++ 2015 之间的重大更改的详细信息,请参阅 Visual C++ 更改历史记录(2003 - 2015)For information on the breaking changes that occurred between Visual C++ 2013 and Visual C++ 2015, see Visual C++ change history 2003 - 2015.

Visual C++ 2015 中的符合性改进Conformance Improvements in Visual C++ 2015

  • /Zc:forScope- 选项/Zc:forScope- option

    编译器选项 /Zc:forScope- 已弃用,并且将在将来版本中删除。The compiler option /Zc:forScope- is deprecated and will be removed in a future release.

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

    以前会经常用到此选项,以便允许非标准代码在点的位置之后使用循环变量,根据标准规范,这些变量本应该在范围之外。The option was usually used in order to allow nonstandard code that uses loop variables after the point where, according to the standard, they should have gone out of scope. 仅当使用 /Za 选项进行编译时才需要,因为没有 /Za,将始终允许在循环结束后使用 for 循环变量。It was only necessary when you are compiling with the /Za option, since without /Za, using a for loop variable after the end of the loop is always allowed. 如果你不关心标准符合性(例如,如果你的代码不是为了移植到其他编译器),你可以关闭 /Za 选项(或将“禁用语言扩展”属性设置为“否”)。If you don't care about standards conformance (for example, if your code isn't meant to portable to other compilers), you could turn off the /Za option (or set the Disable Language Extensions property to No). 如果你确实关心编写可移植且符合标准的代码,则应重写代码,以便通过将此类变量的声明移到循环以外的点使其符合标准。If you do care about writing portable, standards-compliant code, you should rewrite your code so that it conforms to the standard by moving the declaration of such variables to a point outside the loop.

    // 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 编译器选项/Zg compiler option

    /Zg 编译器选项(生成函数原型)不再可用。The /Zg compiler option (Generate Function Prototypes) is no longer available. 此此编译器选项已被弃用。This compiler option was previously deprecated.

  • 你无法再使用 mstest.exe 从命令行运行 C++/CLI 单元测试。You can no longer run unit tests with C++/CLI from the command-line with mstest.exe. 请改用 vstest.console.exe。Instead, use vstest.console.exe. 请参阅 VSTest.Console.exe 命令行选项See VSTest.Console.exe command-line options.

  • 可变关键字mutable keyword

    在之前其正确编译的位置,不再允许存在 mutable 存储类说明符。The mutable storage class specifier is no longer allowed in places where previously it compiled without error. 现在,编译器报告错误 C2071(非法存储类)。Now, the compiler gives error C2071 (illegal storage class). 根据标准,可变说明符仅可应用于类数据成员的名称,不能应用于声明为 const 或 static 的名称,也不能应用于引用成员。According to the standard, the mutable specifier can be applied only to names of class data members, and cannot be applied to names declared const or static, and cannot be applied to reference members.

    例如,考虑以下代码:For example, consider the following code:

    struct S {  
        mutable int &r;  
    };  
    

    早期版本的 Visual C++ 编译器接受此代码,但现在编译器则报告以下错误:Previous versions of the Visual C++ compiler accepted this, but now the compiler gives the following error:

    error C2071: 'S::r': illegal storage class  
    

    若要修复此错误,只需删除冗余的可变关键字。To fix the error, simply remove the redundant mutable keyword.

  • char_16_t 和 char32_tchar_16_t and char32_t

    你不能再使用 char16_tchar32_t 作为 typedef 中的别名,因为这些类型现在被视为内置。You can no longer use char16_t or char32_t as aliases in a typedef, because these types are now treated as built-in. 用户和库作者通常会将 char16_t 和 char32_t 分别定义为 uint16_t 和 uint32_t 的别名。It was common for users and library authors to define char16_t and char32_t as aliases of uint16_t and uint32_t, respectively.

    #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 声明,并重命名与这些名称发生冲突的任何其他标识符。To update your code, remove the typedef declarations and rename any other identifiers that collide with these names.

  • 非类型模板参数Non-type template parameters

    现在会在提供显式模板参数时准确检查包含非类型模板参数的某些代码的类型符合性。Certain code that involves non-type template parameters is now correctly checked for type compatibility when you provide explicit template arguments. 例如,在早期版本的 Visual C++ 中正确编译的以下代码。For example, the following code compiled without error in previous versions of 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):The current compiler correctly gives an error, because the template parameter type doesn't match the template argument (the parameter is a pointer to a const member, but the function f is non-const):

    error C2893: Failed to specialize function template 'void S2::f(void)'note: With the following template arguments:note: 'C=S1'note: 'Function=S1::f'  
    

    若要在代码中修复此错误,请确保你使用的模板自变量类型匹配模板参数声明的类型。To address this error in your code, make sure that the type of the template argument you use matches the declared type of the template parameter.

  • __declspec(align)__declspec(align)

    编译器不再接受函数上的 __declspec(align)The compiler no longer accepts __declspec(align) on functions. 以前会始终忽略此项,但现在会产生编译器错误。This was always ignored, but now it produces a compiler error.

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

    若要解决此问题,请从函数声明中删除 __declspec(align)To fix this problem, remove __declspec(align) from the function declaration. 因为它不起作用,将其删除不会更改任何内容。Since it had no effect, removing it does not change anything.

  • 异常处理Exception handling

    有几个对异常处理的更改。There are a couple of changes to exception handling. 首先,异常对象必须可复制或可移动。First, exception objects have to be either copyable or movable. 在 --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013 中编译的以下代码却不能在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中进行编译:The following code compiled in --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013, but does not compile in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015:

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

    问题在于,复制构造函数是私有的,因此对象无法像处理异常的标准过程那样进行复制。The problem is that the copy constructor is private, so the object cannot be copied as happens in the normal course of handling an exception. 当复制构造函数为声明的 explicit时,这同样适用。The same applies when the copy constructor is declared explicit.

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

    若要更新你的代码,请确保异常对象的复制构造函数是公用的且未标记为 explicitTo update your code, make sure that the copy constructor for your exception object is public and not marked explicit.

    通过值捕获异常还要求异常对象可复制。Catching an exception by value also requires the exception object to be copyable. 在 --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013 中编译的以下代码却不能在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中进行编译:The following code compiled in --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013, but does not compile in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015:

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

    可以通过将 catch 的参数类型更改为引用来解决此问题。You can fix this issue by changing the parameter type for the catch to a reference.

    catch(D& d)  
    {  
    }  
    
  • 后跟宏的字符串文本String literals followed by macros

    编译器现在支持用户定义的文本。The compiler now supports user defined literals. 因此,宏之前没有任何干预空格的字符串文本被视为用户定义的文本,这可能会产生错误或意外结果。As a consequence, string literals followed by macros without any intervening whitespace are interpreted as user-defined literals, which might produce errors or unexpected results. 例如,在早期的编译器中,成功编译了以下代码:For example, in previous compilers the following code compiled successfully:

    #define _x "there"  
    char* func() {  
        return "hello"_x;  
    }  
    int main()  
    {  
        char * p = func();  
        return 0;  
    }  
    

    编译器将此视为后跟宏的字符串文本“hello”,该宏是展开的“there”,然后两个字符串串联成一个。The compiler interpreted this as a string literal "hello" followed by a macro, which is expanded "there", and then the two string literals were concatenated into one. 在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中,编译器将此解释为用户定义的文字,但由于没有定义匹配的用户定义的 _x 文本,它将报告错误。In --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015, the compiler interprets this as a user-defined literal, but since there is no matching user-defined literal _x defined, it gives an error.

    error C3688: invalid literal suffix '_x'; literal operator or literal operator template 'operator ""_x' not found  
    note: Did you forget a space between the string literal and the prefix of the following string literal?  
    

    若要解决此问题,请在字符串文本和宏之间添加一个空格。To fix this problem, add a space between the string literal and the macro.

  • 相邻字符串文本Adjacent string literals

    与上文类似,由于字符串分析中的相关变化,没有任何空格的相邻字符串文本(或宽或窄的字符字符串文本)被视为 Visaul C++ 早期版本中的单个串联字符串。Similarly to the previous, due to related changes in string parsing, adjacent string literals (either wide or narrow character string literals) without any whitespace were interpreted as a single concatenated string in previous releases of Visaul C++. 在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中,现在必须在两个字符串之间添加空格。In --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015, you must now add whitespace between the two strings. 例如,必须更改以下代码:For example, the following code must be changed:

    char * str = "abc""def";  
    

    只需在两个字符串之间添加空间。Simply add a space in between the two strings.

    char * str = "abc" "def";  
    
  • placement new 和 placement deletePlacement new and delete

    对 delete 运算符做出更改以使其符合 C++14 标准。A change has been made to the delete operator in order to bring it into conformance with C++14 standard. 标准更改的详细信息位于 C++ 调整了大小的释放Details of the standards change can be found at C++ Sized Deallocation. 这些更改将添加采用大小参数的全局 delete 运算符的形式。The changes add a form of the global delete operator that takes a size parameter. 重大更改为,如果你之前使用的是具有相同签名的运算符 delete(以与 placement new 运算符对应),你将收到编译器错误(C2956,在使用 placement new 的点位置出现,因为在代码中的该位置,编译器会尝试标识适当匹配的 delete 运算符)。The breaking change is that if you were previously using an operator delete with the same signature (to correspond with a placement new operator), you will receive a compiler error (C2956, which occurs at the point where the placement new is used, since that's the position in code where the compiler tries to identify an appropriate matching delete operator).

    函数 void operator delete(void *, size_t) 是与 C++11 中的 placement new 函数“void * operator new(size_t, size_t)”对应的 placement delete 运算符。The function void operator delete(void *, size_t) was a placement delete operator corresponding to the placement new function "void * operator new(size_t, size_t)" in C++11. 使用 C++14 调整了大小的释放,此 delete 函数现在是 常用释放函数 (全局 delete 运算符)。With C++14 sized deallocation, this delete function is now a usual deallocation function (global delete operator). 标准要求为,如果使用 placement new 查找相应的 delete 函数和常用释放函数,则程序会出现格式错误。The standard requires that if the use of a placement new looks up a corresponding delete function and finds a usual deallocation function, the program is ill-formed.

    例如,假设你的代码同时定义了 placement new 和 placement delete:For example, suppose your code defines both a placement new and a placement delete:

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

    由于定义的 placement delete 运算符和新的全局调整大小的 delete 运算符之间的函数签名匹配,因此就会出现问题。The problem occurs because of the match in function signatures between a placement delete operator you've defined, and the new global sized delete operator. 考虑是否可以使用任何 placement new 和 placement delete 运算符的其他类型(size_t 除外)。Consider whether you can use a different type other than size_t for any placement new and delete operators. 请注意,size_t typedef 的类型取决于编译器;在 Visual C++ 中,它是一个无符号整型的 typedef。Note that the type of the size_t typedef is compiler-dependent; it is a typedef for unsigned int in Visual C++. 较好的解决办法就是使用如下的枚举类型:A good solution is to use an enumerated type such as this:

    enum class my_type : size_t {};  
    

    然后,更改你对 placement new 和 placement delete 的定义,以使用此类型作为第二个参数(而不是 size_t)。Then, change your definition of placement new and delete to use this type as the second argument instead of size_t. 你还需要更新对 placement new 的调用以传递新类型(例如,通过使用 static_cast<my_type> 从整数值转换)并更新 new 和 delete 的定义以强制转换回整数类型。You’ll also need to update the calls to placement new to pass the new type (for example, by using static_cast<my_type> to convert from the integer value) and update the definition of new and delete to cast back to the integer type. 你无需为此使用枚举;具有 size_t 成员的类类型也将起作用。You don’t need to use an enum for this; a class type with a size_t member would also work.

    你还可以将 placement new 全部消除作为备选解决方案。An alternative solution is that you might be able to eliminate the placement new altogether. 如果你的代码使用 placement new 实现内存池,其中位置参数是分配或删除的对象的大小,则调整了大小的释放功能可能适合替换你自定义的内存池代码,且你可以去掉位置函数,仅使用自己两个参数的 delete 运算符(而不是位置函数)。If your code uses placement new to implement a memory pool where the placement argument is the size of the object being allocated or deleted, then sized deallocation feature might be suitable to replace your own custom memory pool code, and you can get rid of the placement functions and just use your own two-argument delete operator instead of the placement functions.

    如果你不想立即更新代码,可以通过使用编译器选项 /Zc:sizedDealloc- 恢复到旧行为。If you don't want to update your code immediately, you can revert to the old behavior by using the compiler option /Zc:sizedDealloc-. 如果使用此选项,则不存在两个参数的 delete 函数,并且也不会导致与 placement delete 运算符发生冲突。If you use this option, the two-argument delete functions don’t exist and won't cause a conflict with your placement delete operator.

  • 联合数据成员Union data members

    联合数据成员不再具有引用类型。Data members of unions can no longer have reference types. 以下代码在 --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013中成功编译,但在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中产生错误。The following code compiled successfully in --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013, but produces an error in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015.

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

    前面的代码产生以下错误:The preceding code produces the following errors:

    test.cpp(67): error C2625: 'U2::i': illegal union member; type 'int &' is reference type  
    test.cpp(70): error C2625: 'U3::i': illegal union member; type 'int &' is reference type  
    

    若要解决此问题,请将引用类型更改为指针或值。To address this issue, change reference types either to a pointer or a value. 更改指针类型需要对使用联合字段的代码进行更改。Changing the type to a pointer requires changes in the code that uses the union field. 将代码更改为值将更改存储在联合中的数据,这会影响其他字段,因为联合类型中的字段共享相同的内存。Changing the code to a value would change the data stored in the union, which affects other fields since fields in union types share the same memory. 根据值的大小,它还可能更改联合的大小。Depending on the size of the value, it might also change the size of the union.

  • 匿名联合现在更符合标准。Anonymous unions are now more conformant to the standard. 早期版本的编译器生成了匿名联合的显式构造函数和析构函数。Previous versions of the compiler generated an explicit constructor and destructor for anonymous unions. 这些在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中已删除。These are deleted in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015.

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

    前面的代码在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中生成以下错误:The preceding code generates the following error in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015:

    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  
    

    若要解决此问题,请提供你对构造函数和/或析构函数的定义。To resolve this issue, provide your own definitions of the constructor and/or destructor.

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

    为了符合标准,已对联合中的匿名结构的成员更改了运行时行为。In order to conform with the standard, the runtime behavior has changed for members of anonymous structures in unions. 创建此类联合时,将不再隐式调用联合中的匿名结构成员的构造函数。The constructor for anonymous structure members in a union is no longer implicitly called when such a union is created. 此外,联合超出范围时,不再隐式调用联合中的匿名结构成员的析构函数。Also, the destructor for anonymous structure members in a union is no longer implicitly called when the union goes out of scope. 请考虑以下代码,其中联合 U 包含一个匿名结构,此匿名结构包含的成员是一个具有析构函数的命名结构 S。Consider the following code, in which a union U contains an anonymous structure that contains a member which is a named structure S that has a destructor.

    #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;  
    }  
    

    在 --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013 中,创建联合时会调用 S 的构造函数,清理函数 f 的堆栈时会调用 S 的析构函数。In --- --- Visual Studio 2013 中的 Visual C++Visual C++ in Visual Studio 2013, the constructor for S is called when the union is created, and the destructor for S is called when the stack for function f is cleaned up. 但在 --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015 中,不会调用构造函数和析构函数。But in --- --- Visual Studio 2015 中的 Visual C++Visual C++ in Visual Studio 2015, the constructor and destructor are not called. 编译器会对关于此行为的更改发出警告。The compiler gives a warning about this behavior change.

    warning C4587: 'U::s': behavior change: constructor is no longer implicitly calledwarning C4588: 'U::s': behavior change: destructor is no longer implicitly called  
    

    若要还原原始行为,请赋予匿名结构一个名称。To restore the original behavior, give the anonymous structure a name. 无论编译器版本为何,非匿名结构的运行时行为都是相同的。The runtime behavior of non-anonymous structures is the same, regardless of the compiler version.

    #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;  
    }  
    

    或者,尝试将构造函数和析构函数代码移到新的函数中,并从联合的构造函数和析构函数添加对这些函数的调用。Alternatively, try moving the constructor and destructor code into new functions, and add calls to these functions from the constructor and destructor for the union.

    #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;  
    }  
    
  • 模板解析Template resolution

    对模板的名称解析进行了更改。Changes have been made to name resolution for templates. 在 C++ 中,考虑名称解析的候选对象时,可能会出现作为潜在匹配项考虑的一个或多个名称生成无效的模板实例化的情况。In C++, when considering candidates for the resolution of a name, it can be the case that one or more names under consideration as potential matches produces an invalid template instantiation. 这些无效的实例化通常不会导致编译器错误,这被称为 SFINAE(替换失败不是错误)原则。These invalid instantiations do not normally cause compiler errors, a principle which is known as SFINAE (Substitution Failure Is Not An Error).

    现在,如果 SFINAE 要求编译器将类模板专用化进行实例化,则在此过程中发生的任何错误都是编译器错误。Now, if SFINAE requires the compiler to instantiate the specialization of a class template, then any errors that occur during this process are compiler errors. 在早期版本中,编译器会忽略此类错误。In previous versions, the compiler would ignore such errors. 例如,考虑以下代码:For example, consider the following code:

    #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);  
    }  
    

    如果使用当前编译器进行编译,将得到以下错误:If you compile with the current compiler, you get the following error:

    type_traits(1110): error C2139: 'D': an undefined class is not allowed as an argument to compiler intrinsic type trait '__is_base_of'  
    ..\t331.cpp(14): note: see declaration of 'D'  
    ..\t331.cpp(10): note: see reference to class template instantiation 'std::is_base_of<T,U>' being compiled  
            with  
            [  
                T=D,  
                U=D  
            ]  
    

    这是因为在第一次调用 is_base_of 时,尚未定义类“D”。This is because at the point of the first invocation of the is_base_of the class 'D' has not yet been defined.

    在这种情况下,解决方法是在定义类之前,不使用此类类型特征。In this case, the fix is not to use such type traits until the class has been defined. 如果将 D 和 B 的定义移到代码文件的开头,错误将得到解决。If you move the definitions of B and D to the beginning of the code file, the error is resolved. 如果定义位于标头文件中,请检查标头文件的 include 语句的顺序,以确保在使用有问题的模板之前,对任何类定义进行了编译。If the definitions are in header files, check the order of the include statements for the header files to make sure that any class definitions are compiled before the problematic templates are used.

  • 复制构造函数Copy constructors

    在 --- --- Visual Studio 2013Visual Studio 2013 和 Visual Studio 2015 中,如果该类具有用户定义的移动构造函数,但没有用户定义的复制构造函数,则编译器生成类的复制构造函数。In both --- --- Visual Studio 2013Visual Studio 2013 and Visual Studio 2015, the compiler generates a copy constructor for a class if that class has a user-defined move constructor but no user-defined copy constructor. 在 Dev14 中,此隐式生成的复制构造函数也标记为“= delete”。In Dev14, this implicitly generated copy constructor is also marked "= delete".

更新 1 中的符合性改进Conformance Improvements in Update 1

  • 私有虚拟基类和间接继承Private virtual base classes and indirect inheritance

    早期版本的编译器允许派生类调用 间接派生private virtual 基类的成员函数。Previous versions of the compiler allowed a derived class to call member functions of its indirectly-derivedprivate virtual base classes. 这种旧行为不正确,也不符合 C++ 标准。This old behavior was incorrect and does not conform to the C++ standard. 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2280。The compiler no longer accepts code written in this way and issues compiler error C2280 as a result.

    error C2280: 'void *S3::__delDtor(unsigned int)': attempting to reference a deleted function  
    

    示例(之前)Example (before)

    class base  
    {  
    protected:  
        base();  
        ~base();  
    };  
    
    class middle: private virtual base {};class top: public virtual middle {};  
    
    void destroy(top *p)  
    {  
        delete p;  //   
    }  
    

    示例(之后)Example (after)

    class base;  // as above  
    
    class middle: protected virtual base {};  
    class top: public virtual middle {};  
    
    void destroy(top *p)  
    {  
        delete p;  
    }  
    

    - 或 --or-

    class base;  // as above  
    
    class middle: private virtual base {};  
    class top: public virtual middle, private virtual bottom {};  
    
    void destroy(top *p)  
    {  
        delete p;  
    }  
    
  • 重载的 new 运算符和 delete 运算符Overloaded operator new and operator delete

    早期版本的编译器允许非成员 operator new 和非成员 operator delete 声明为静态,并在全局命名空间之外的命名空间中声明。Previous versions of the compiler allowed non-member operator new and non-member operator delete to be declared static, and to be declared in namespaces other than the global namespace. 这种旧行为会引发风险,导致程序无法按按程序员的预期调用 newdelete 运算符实现,从而导致无提示的运行时行为错误。This old behavior created a risk that the program would not call the new or delete operator implementation that the programmer intended, resulting in silent bad runtime behavior. 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2323。The compiler no longer accepts code written in this way and issues compiler error C2323 instead.

    error C2323: 'operator new': non-member operator new or delete functions may not be declared static or in a namespace other than the global namespace.  
    

    示例(之前)Example (before)

    static inline void * __cdecl operator new(size_t cb, const std::nothrow_t&)  // error C2323  
    

    示例(之后)Example (after)

    void * __cdecl operator new(size_t cb, const std::nothrow_t&)  // removed 'static inline'  
    

    此外,尽管编译器不能进行具体诊断,但内联运算符 new 会被视为格式不正确。Additionally, although the compiler doesn't give a specific diagnostic, inline operator new is considered ill-formed.

  • 对非类类型调用“operator type()”(用户定义的转换)Calling 'operator type()' (user-defined conversion) on non-class types

    早期版本的编译器允许以无提示忽略的方式对非类类型调用“operator type()”。Previous versions of the compiler allowed 'operator type()' to be called on non-class types while silently ignoring it. 这种旧行为会导致无提示代码生成错误风险,从而导致不可预知的运行时行为。This old behavior created a risk of silent bad code generation, resulting in unpredictable runtime behavior. 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2228。The compiler no longer accepts code written in this way and issues compiler error C2228 instead.

    error C2228: left of '.operator type' must have class/struct/union  
    

    示例(之前)Example (before)

    typedef int index_t;  
    
    void bounds_check(index_t index);  
    
    void login(int column)  
    {  
        bounds_check(column.operator index_t());  // error C2228  
    }  
    

    示例(之后)Example (after)

    typedef int index_t;  
    
    void bounds_check(index_t index);  
    
    void login(int column)  
    {  
        bounds_check(column);  // removed cast to 'index_t', 'index_t' is an alias of 'int'  
    }  
    
  • 详细的类型说明符中的多余 typenameRedundant typename in elaborated type specifiers

    早期版本的编译器允许详细的类型说明符中出现 typename ;用这种方式编写的代码在语义上不正确。Previous versions of the compiler allowed typename in an elaborated type specifiers; code written in this way is semantically incorrect. 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C3406。The compiler no longer accepts code written in this way and issues compiler error C3406 instead.

    error C3406: 'typename' cannot be used in an elaborated type specifier  
    

    示例(之前)Example (before)

    template <typename class T>  
    class container;  
    

    示例(之后)Example (after)

    template <class T>  // alternatively, could be 'template <typename T>'; 'typename' is not elaborating a type specifier in this case  
    class container;  
    
  • 初始值设定项列表中数组的类型推断Type deduction of arrays from an initializer list

    早期版本的编译器不支持对初始值设定项列表中的数组进行类型推断。Previous versions of the compiler did not support type deduction of arrays from an initializer list. 编译器现在支持这种形式的类型推断,因此调用使用初始值设定项列表的函数模板现在可能会不明确,或者选择一个与以前版本的编译器不同的重载。The compiler now supports this form of type deduction and, as a result, calls to function templates using initializer lists might now be ambiguous or a different overload might be chosen than in previous versions of the compiler. 要解决这些问题,程序现在必须显式指定程序员所需的重载。To resolve these issues, the program must now explicitly specify the overload that the programmer intended.

    当这一新行为导致重载解决方法要考虑与以往候选一样好的其他候选时,调用变得不明确,编译器会发出编译器错误 C2668。When this new behavior causes overload resolution to consider an additional candidate that is equally as good as the historic candidate, the call becomes ambiguous and the compiler issues compiler error C2668 as a result.

    error C2668: 'function' : ambiguous call to overloaded function.  
    

    示例 1: 对重载函数的调用不明确(之前)Example 1: Ambiguous call to overloaded function (before)

    // In previous versions of the compiler, code written in this way would unambiguously call f(int, Args...)  
    template <typename... Args>  
    void f(int, Args...);  //   
    
    template <int N, typename... Args>  
    void f(const int (&)[N], Args...);  
    
    int main()  
    {  
        // The compiler now considers this call ambiguous, and issues a compiler error  
        f({3});  error C2668: 'f' ambiguous call to overloaded function  
    }  
    

    示例 1: 对重载函数的调用不明确(之后)Example 1: ambiguous call to overloaded function (after)

    template <typename... Args>  
    void f(int, Args...);  //   
    
    template <int N, typename... Args>  
    void f(const int (&)[N], Args...);  
    
    int main()  
    {  
        // To call f(int, Args...) when there is just one expression in the initializer list, remove the braces from it.  
        f(3);  
    }  
    

    这一新行为会导致重载解决方法要考虑比以往候选更适合的其他候选时,调用将明确地解析为新候选,导致程序行为的更改可能与程序员的需要有所不同。When this new behavior causes overload resolution to consider an additional candidate that is a better match than the historic candidate, the call resolves unambiguously to the new candidate, causing a change in program behavior that is probably different than the programmer intended.

    示例 2:重载解决方法的更改(之前)Example 2: change in overload resolution (before)

    // In previous versions of the compiler, code written in this way would unambiguously call f(S, Args...)  
    struct S  
    {  
        int i;  
        int j;  
    };  
    
    template <typename... Args>  
    void f(S, Args...);  
    
    template <int N, typename... Args>  
    void f(const int *&)[N], Args...);  
    
    int main()  
    {  
        // The compiler now resolves this call to f(const int (&)[N], Args...) instead  
        f({1, 2});  
    }  
    

    示例 2:重载解决方法的更改(之后)Example 2: change in overload resolution (after)

    struct S;  // as before  
    
    template <typename... Args>  
    void f(S, Args...);  
    
    template <int N, typename... Args>  
    void f(const int *&)[N], Args...);  
    
    int main()  
    {  
        // To call f(S, Args...), perform an explicit cast to S on the initializer list.  
        f(S{1, 2});  
    }  
    
  • switch 语句警告的还原Restoration of switch statement warnings

    前一个版本的编译器删除了之前存在的与 switch 语句相关的警告;现在已还原所有这些警告。A Previous version of the compiler removed previously-existing warnings related to switch statements; these warnings have now been restored. 编译器现在将发出还原的警告,并且现在会在包含有问题用例的行中发出与特定用例(包括默认情况下)相关的警告,而不是在 switch 语句的最后一行发出。The compiler now issues the restored warnings, and warnings related to specific cases (including the default case) are now issued on the line containing the offending case, rather than on the last line of the switch statement. 因此,现在发出这些警告的行与过去不同,按照需要使用 #pragma warning(disable:####) 可不再禁止显示以前禁止显示的警告。As a result of now issuing those warnings on different lines than in the past, warnings previously suppressed by using #pragma warning(disable:####) may no longer be suppressed as intended. 要按照需要禁止显示这些警告,可能需要将 #pragma warning(disable:####) 指令移到第一个可能有问题的用例上面的行。To suppress these warnings as intended, it might be necessary to move the #pragma warning(disable:####) directive to a line above the first potentially-offending case. 以下是还原的警告。The following are the restored warnings.

    warning C4060: switch statement contains no 'case' or 'default' labels  
    
    warning C4061: enumerator 'bit1' in switch of enum 'flags' is not explicitly handled by a case label  
    
    warning C4062: enumerator 'bit1' in switch of enum 'flags' is not handled  
    
    warning C4063: case 'bit32' is not a valid value for switch of enum 'flags'  
    
    warning C4064: switch of incomplete enum 'flags'  
    
    warning C4065: switch statement contains 'default' but no 'case' labels  
    
    warning C4808: case 'value' is not a valid value for switch condition of type 'bool'  
    
    Warning C4809: switch statement has redundant 'default' label; all possible 'case' labels are given  
    

    C4063 示例(之前)Example of C4063 (before)

    class settings  
    {  
    public:  
        enum flags  
        {  
            bit0 = 0x1,  
            bit1 = 0x2,  
            ...  
        };  
        ...  
    };  
    
    int main()  
    {  
        auto val = settings::bit1;  
    
        switch (val)  
        {  
        case settings::bit0:  
            break;  
    
        case settings::bit1:  
            break;  
    
        case settings::bit0 | settings::bit1:  // warning C4063  
            break;  
        }  
    };  
    

    C4063 示例(之后)Example of C4063 (after)

    class settings {...};  // as above  
    
    int main()  
    {  
        // since C++11, use std::underlying_type to determine the underlying type of an enum  
        typedef std::underlying_type<settings::flags>::type flags_t;  
    
        auto val = settings::bit1;  
    
        switch (static_cast<flags_t>(val))  
        {  
        case settings::bit0:  
            break;  
    
        case settings::bit1:  
            break;  
    
        case settings::bit0 | settings::bit1:  // ok  
            break;  
        }  
    };  
    

    在其文档中提供了其他还原警告的示例。Examples of the other restored warnings are provided in their documentation.

  • #include:在路径名中使用父目录说明符“..” (只影响 /Wall/WX)#include: use of parent-directory specifier '..' in pathname (only affects /Wall /WX)

    早期版本的编译器没有检测到使用父目录说明符“..”Previous versions of the compiler did not detect the use of the parent-directory specifier '..' (在 #include 指令的路径名中)。in the pathname of #include directives. 以这种方式编写的代码通常用于包含因不正确使用项目相对路径而留在项目外的标头。Code written in this way is usually intended to include headers that exist outside of the project by incorrectly using project-relative paths. 这一旧行为会引发风险,导致编译程序时包含了程序员不需要的源文件来,或这些相对路径不能移植到其他生成环境中。This old behavior created a risk that the program could be compiled by including a different source file than the programmer intended, or that these relative paths would not be portable to other build environments. 编译器现在会检测以这种方式编写的代码并通知程序员,并发出可选编译器警告 C4464(如果已启用)。The compiler now detects and notifies the programmer of code written in this way and issues an optional compiler warning C4464, if enabled.

    warning C4464: relative include path contains '..'  
    

    示例(之前)Example (before)

    #include "..\headers\C4426.h"  // emits warning C4464  
    

    示例(之后)Example (after)

    #include "C4426.h"  // add absolute path to 'headers\' to your project's include directories  
    

    此外,虽然编译器并不会进行具体诊断,但建议不应将父目录说明符“..”用于指定项目的包含目录。Additionally, although the compiler does not give a specific diagnostic, we also recommend that the parent-directory specifier ".." should note be used to specify your project's include directories.

  • #pragma optimize() 超出标头文件的末尾 (只影响 /Wall/WX)#pragma optimize() extends past end of header file (only affects /Wall /WX)

    早期版本的编译器无法检测到对转义翻译单元中包含的标头文件的优化标志设置的更改。Previous versions of the compiler did not detect changes to optimization flag settings that escape a header file included within a translation unit. 编译器现在会检测以这种方式编写的代码并通知程序员,并在有问题的 #include的位置发出可选编译器警告 C4426(如果已启用)。The compiler now detects and notifies the programmer of code written in this way and issues an optional compiler warning C4426 at the location of the offending #include, if enabled. 只有更改与编译器命令行参数设置的优化标志发生冲突时,才发出此警告。This warning is only issued if the changes conflict with the optimization flags set by command-line arguments to the compiler.

    warning C4426: optimization flags changed after including header, may be due to #pragma optimize()  
    

    示例(之前)Example (before)

    // C4426.h  
    #pragma optimize("g", off)  
    ...  
    // C4426.h ends  
    
    // C4426.cpp  
    #include "C4426.h"  // warning C4426  
    

    示例(之后)Example (after)

    // C4426.h  
    #pragma optimize("g", off)  
    ...  
    #pragma optimize("", on)  // restores optimization flags set via command-line arguments  
    // C4426.h ends  
    
    // C4426.cpp  
    #include "C4426.h"  
    
  • #pragma warning(push)#pragma warning(pop) (只影响 /Wall/WX)Mismatched #pragma warning(push) and #pragma warning(pop) (only affects /Wall /WX)

    早期版本的编译器无法检测到不同源文件中与 #pragma warning(pop) 状态更改配对的 #pragma warning(push) 状态更改,这并不是我们所预期的。Previous versions of the compiler did not detect #pragma warning(push) state changes being paired with #pragma warning(pop) state changes in a different source file, which is rarely intended. 这种旧行为会引发风险,导致程序编译时会启用一组程序员不希望出现的警告,可能会导致无提示的运行时行为错误。This old behavior created a risk that the program would be compiled with a different set of warnings enabled than the programmer intended, possibly resulting in silent bad runtime behavior. 编译器现在能够检测以这种方式编写的代码并通知程序员,并在匹配 #pragma warning(pop) 位置发出可选编译器警告 C5031(如果已启用)。The compiler now detects and notifies the programmer of code written in this way and issues an optional compiler warning C5031 at the location of the matching #pragma warning(pop), if enabled. 此警告包括引用相应 #pragma warning(push) 的位置的注释。This warning includes a note referencing the location of the corresponding #pragma warning(push).

    warning C5031: #pragma warning(pop): likely mismatch, popping warning state pushed in different file  
    

    示例(之前)Example (before)

    // C5031_part1.h  
    #pragma warning(push)  
    #pragma warning(disable:####)  
    ...  
    // C5031_part1.h ends without #pragma warning(pop)  
    
    // C5031_part2.h  
    ...  
    #pragma warning(pop)  // pops a warning state not pushed in this source file   
    ...  
    // C5031_part1.h ends  
    
    // C5031.cpp  
    #include "C5031_part1.h" // leaves #pragma warning(push) 'dangling'  
    ...  
    #include "C5031_part2.h" // matches 'dangling' #pragma warning(push), resulting in warning C5031  
    ...  
    

    示例(之后)Example (after)

    // C5031_part1.h  
    #pragma warning(push)  
    #pragma warning(disable:####)  
    ...  
    #pragma warning(pop)  // pops the warning state pushed in this source file  
    // C5031_part1.h ends without #pragma warning(pop)  
    
    // C5031_part2.h  
    #pragma warning(push)  // pushes the warning state pushed in this source file  
    #pragma warning(disable:####)  
    ...  
    #pragma warning(pop)  
    // C5031_part1.h ends  
    
    // C5031.cpp  
    #include "C5031_part1.h" // #pragma warning state changes are self-contained and independent of other source files or their #include order.  
    ...  
    #include "C5031_part2.h"  
    ...  
    

    虽然不常见,但是有时会故意以这种方式编写代码。Though uncommon, code written in this way is sometimes intentional. 以这种方式编写的代码对于 #include 顺序的更改比较敏感;如果可能,我们建议源代码文件以自包含的方式管理警告状态。Code written in this way is sensitive to changes in #include order; when possible, we recommend that source code files manage warning state in a self-contained way.

  • #pragma warning(push) 不匹配 (只影响 /Wall/WX)Unmatched #pragma warning(push) (only affects /Wall /WX)

    早期版本的编译器无法检测到翻译单元末尾出现的不匹配 #pragma warning(push) 状态更改。Previous versions of the compiler did not detect unmatched #pragma warning(push) state changes at the end of a translation unit. 编译器现在能够检测以这种方式编写的代码并通知程序员,并在不匹配的 #pragma warning(push) 位置发出可选编译器警告 C5032(如果已启用)。The compiler now detects and notifies the programmer of code written in this way and issues an optional compiler warning C5032 at the location of the unmatched #pragma warning(push), if enabled. 只有翻译单元中没有任何编译错误时,才会发出此警告。This warning is only issued if there are no compilation errors in the translation unit.

    warning C5032: detected #pragma warning(push) with no corresponding #pragma warning(pop)  
    

    示例(之前)Example (before)

    // C5032.h  
    #pragma warning(push)  
    #pragma warning(disable:####)  
    ...  
    // C5032.h ends without #pragma warning(pop)  
    
    // C5032.cpp  
    #include "C5032.h"  
    ...  
    // C5032.cpp ends -- the translation unit is completed without #pragma warning(pop), resulting in warning C5032 on line 1 of C5032.h  
    

    示例(之后)Example (after)

    // C5032.h  
    #pragma warning(push)  
    #pragma warning(disable:####)  
    ...  
    #pragma warning(pop) // matches #pragma warning (push) on line 1  
    // C5032.h ends  
    
    // C5032.cpp  
    #include "C5032.h"  
    ...  
    // C5032.cpp ends -- the translation unit is completed without unmatched #pragma warning(push)  
    
  • #pragma 警告状态跟踪改进后可能会发出更多警告Additional warnings might be issued as a result of improved #pragma warning state tracking

    早期版本的编译器无法有效跟踪 #pragma 警告状态更改,因而无法发出所有所需的警告。Previous versions of the compiler tracked #pragma warning state changes insufficiently well to issue all intended warnings. 这种行为会引发风险,导致在程序不希望的情况下有效禁止显示某些警告。This behavior created a risk that certain warnings would be effectively suppressed in circumstances different than the programmer intended. 编译器现在能够更加可靠地跟踪 #pragma 警告状态,尤其与模板内部的 #pragma 警告状态更改相关,并选择性发出新警告 C5031 和 C5032,旨在帮助程序员找到意外使用 #pragma warning(push)#pragma warning(pop)The compiler now tracks #pragma warning state more robustly -- especially related to #pragma warning state changes inside of templates -- and optionally issues new warnings C5031 and C5032 which are intended to help the programmer locate unintended uses of #pragma warning(push) and #pragma warning(pop).

    由于改进了 #pragma 警告状态更改跟踪,现在可能会发出以前错误地禁止显示的警告或与以前误诊问题的相关警告。As a result of improved #pragma warning state change tracking, warnings formerly incorrectly suppressed or warnings related to issues formerly misdiagnosed might now be issued.

  • 对无法访问代码标识的改进Improved identification of unreachable code

    针对早期版本的编译器进行的 C++ 标准库的更改和内联函数调用能力改进可能会使编译器能够证明某些代码现在无法访问。C++ Standard Library changes and improved ability to inline function calls over previous versions of the compiler might allow the compiler to prove that certain code is now unreachable. 这一新行为可能导致新警告并更频繁地发出警告 C4720 实例。This new behavior can result in new and more-frequently issued instances of warning C4720.

    warning C4720: unreachable code  
    

    在许多情况下,只有启用优化进行编译时,才会发出此警告,因为优化可能嵌入更多函数调用,消除冗余代码或者能够确定某些代码是否无法访问。In many cases, this warning might only be issued when compiling with optimizations enabled, since optimizations may inline more function calls, eliminate redundant code, or otherwise make it possible to determine that certain code is unreachable. 我们观察到,警告 C4720 的新实例在 try/catch 块中经常发生,尤其是在使用 std::find时。We have observed that new instances of warning C4720 have frequently occurred in try/catch blocks, especially in relation to use of std::find.

    示例(之前)Example (before)

    try   
    {   
        auto iter = std::find(v.begin(), v.end(), 5);   
    }   
    catch(...)   
    {   
        do_something();  // ok   
    }  
    

    示例(之后)Example (after)

    try   
    {   
        auto iter = std::find(v.begin(), v.end(), 5);   
    }   
    catch(...)   
    {   
        do_something();  // warning C4702: unreachable code  
    }  
    

更新 2 中的符合性改进Conformance Improvements in Update 2

  • 可能会因对表达式 SFINAE 的部分支持而发出其他警告和错误Additional warnings and errors might be issued as a result of partial support for expression SFINAE

    由于缺少对表达式 SFINAE 的支持,编译器的早期版本无法分析 decltype 说明符中特定类型的表达式。Previous versions of the compiler did not parse certain kinds of expressions inside decltype specifiers due to lack of support for expression SFINAE. 这种旧行为不正确,也不符合 C++ 标准。This old behavior was incorrect and does not conform to the C++ standard. 由于持续的符合性改进,此编译器现已可分析这些表达式,并能为表达式 SFINAE 提供部分支持。The compiler now parses these expressions and has partial support for expression SFINAE due to ongoing conformance improvements. 因此,此编译器现在可发出在编译器的早期版本无法分析的表达式中找到的警告和错误。As a result, the compiler now issues warnings and errors found in expressions that previous versions of the compiler did not parse.

    此新行为分析包含尚未声明类型的 decltype 表达式时,将导致编译器发出编译器错误 C2039。When this new behavior parses a decltype expression that includes a type that has not yet been declared, the compiler issues compiler error C2039 as a result.

    error C2039: 'type': is not a member of '`global namespace''  
    

    示例 1:使用未声明的类型(之前)Example 1: use of an undeclared type (before)

    struct s1  
    {  
      template <typename T>  
      auto f() -> decltype(s2<T>::type::f());  // error C2039  
    
      template<typename>  
      struct s2 {};  
    }  
    

    示例 1(之后)Example 1 (after)

    struct s1  
    {  
      template <typename>  // forward declare s2struct s2;  
    
      template <typename T>  
      auto f() -> decltype(s2<T>::type::f());  
    
      template<typename>  
      struct s2 {};  
    }  
    

    此新行为分析 decltype 表达式时(该表达式缺少将依赖名称指定为类型所必须使用的关键字 typename),编译器将发出编译器警告 C4346 和编译器错误 C2923。When this new behavior parses a decltype expression that is missing a necessary use of the typename keyword to specify that a dependent name is a type, the compiler issues compiler warning C4346 together with compiler error C2923.

    warning C4346: 'S2<T>::Type': dependent name is not a type  
    
    error C2923: 's1': 'S2<T>::Type' is not a valid template type argument for parameter 'T'  
    

    示例 2:依赖名称不是类型(之前)Example 2: dependent name is not a type (before)

    template <typename T>  
    struct s1  
    {  
      typedef T type;  
    };  
    
    template <typename T>  
    struct s2  
    {  
      typedef T type;  
    };  
    
    template <typename T>  
    T declval();  
    
    struct s  
    {  
      template <typename T>  
      auto f(T t) -> decltype(t(declval<S1<S2<T>::type>::type>()));  // warning C4346, error C2923  
    };  
    

    示例 2(之后)Example 2 (after)

    template <typename T> struct s1 {...};  // as above  
    template <typename T> struct s2 {...};  // as above  
    
    template <typename T>  
    T declval();  
    
    struct s  
    {  
      template <typename T>  
      auto f(T t) -> decltype(t(declval<S1<typename S2<T>::type>::type>()));  
    };  
    
  • volatile 成员变量将防止出现隐式定义的构造函数和赋值运算符volatile member variables prevent implicitly defined constructors and assignment operators

    编译器的早期版本允许具有 volatile 成员变量的类自动生成默认复制/移动构造函数和默认复制/移动赋值运算符。Previous versions of the compiler allowed a class that has volatile member variables to have default copy/move constructors and default copy/move assignment operators automatically generated. 这种旧行为不正确,也不符合 C++ 标准。This old behavior was incorrect and does not conform to the C++ standard. 编译器现在认为拥有可变成员变量的类具有非常用构造函数和赋值运算符,这将防止自动生成这些运算符的默认实现。The compiler now considers a class that has volatile member variables to have non-trivial construction and assignment operators which prevents default implementations of these operators from being automatically generated. 当此类为某一联合(或类中的匿名联合)的成员时,会将联合(或包含匿名联合的类)的复制/移动构造函数和复制/移动赋值运算符的隐式定义为已删除。When such a class is a member of a union (or an anonymous union inside of a class), the copy/move constructors and copy/move assignment operators of the union (or the class containing the unonymous union) will be implicitly defined as deleted. 尝试构造或复制联合(或包含匿名联合的类)而不显式定义它们是错误的,将导致编译器发出编译器错误 C2280。Attempting to construct or copy the union (or class containing the anonymous union) without explicitly defining them is an error and the compiler issues compiler error C2280 as a result.

    error C2280: 'B::B(const B &)': attempting to reference a deleted function  
    

    示例(之前)Example (before)

    struct A  
    {  
      volatile int i;  
      volatile int j;  
    };  
    
    extern A* pa;  
    
    struct B  
    {  
      union  
      {  
        A a;  
        int i;  
      };  
    };  
    
    B b1 {*pa};  
    B b2 (b1);  // error C2280  
    

    示例(之后)Example (after)

    struct A  
    {  
      int i;int j;  
    };  
    
    extern volatile A* pa;  
    
    A getA()  // returns an A instance copied from contents of pa  
    {  
      A a;  
      a.i = pa->i;  
      a.j = pa->j;  
      return a;  
    }  
    
    struct B;  // as above  
    
    B b1 {GetA()};  
    B b2 (b1);  // error C2280  
    
  • 静态成员函数不支持 cv 限定符。Static member functions do not support cv-qualifiers.

    Visual C++ 2015 的早期版本允许静态成员函数具有 cv 限定符。Previous versions of Visual C++ 2015 allowed static member functions to have cv-qualifiers. 此行为是由于 Visual C++ 2015 和 Visual C++ 2015 Update 1 中的回归而导致的;Visual C++ 2013 和 Visual C++ 的早期版本拒绝接受以这种方式编写的代码。This behavior is due to a regression in Visual C++ 2015 and Visual C++ 2015 Update 1; Visual C++ 2013 and previous versions of Visual C++ reject code written in this way. Visual C++ 2015 和 Visual C++ 2015 Update 1 的行为不正确且不符合 C++ 标准。The behavior of Visual C++ 2015 and Visual C++ 2015 Update 1 is incorrect and does not conform to the C++ standard. Visual Studio 2015 Update 2 拒绝接受以这种方式编写的代码,并改为发出编译器错误 C2511。Visual Studio 2015 Update 2 rejects code written in this way and issues compiler error C2511 instead.

    error C2511: 'void A::func(void) const': overloaded member function not found in 'A'  
    

    示例(之前)Example (before)

    struct A  
    {  
      static void func();  
    };  
    
    void A::func() const {}  // C2511  
    

    示例(之后)Example (after)

    struct A  
    {  
      static void func();  
    };  
    
    void A::func() {}  // removed const  
    
  • WinRT 代码中不允许枚举的前向声明(仅影响 /ZW)Forward declaration of enum is not allowed in WinRT code (affects /ZW only)

    为 Windows 运行时 (WinRT) 编译的代码不允许前向声明 enum 类型,这与使用 /clr 编译器开关为 .Net Framework 编译托管 C++ 代码时相似。Code compiled for the Windows Runtime (WinRT) doesn't allow enum types to be forward declared, similarly to when managed C++ code is compiled for the .Net Framework using the /clr compiler switch. 此行为可确保枚举大小始终为已知,并可将其正确映射到 WinRT 类型系统。This behavior is ensures that the size of an enumeration is always known and can be correctly projected to the WinRT type system. 编译器将拒绝接受以这种方式编写的代码,并发出编译器错误 C2599 和编译器错误 C3197。The compiler rejects code written in this way and issues compiler error C2599 together with compiler error C3197.

    error C2599: 'CustomEnum': the forward declaration of a WinRT enum is not allowed  
    
    error C3197: 'public': can only be used in definitions  
    

    示例(之前)Example (before)

    namespace A {  
      public enum class CustomEnum: int32;  // forward declaration; error C2599, error C3197  
    }  
    
    namespace A {  
      public enum class CustomEnum: int32  
      {  
        Value1  
      };  
    }  
    
    public ref class Component sealed  
    {  
    public:  
      CustomEnum f()  
      {  
        return CustomEnum::Value1;  
      }  
    };  
    

    示例(之后)Example (after)

              // forward declaration of CustomEnum removed  
    
    namespace A {  
      public enum class CustomEnum: int32  
      {  
        Value1  
      };  
    }  
    
    public ref class Component sealed  
    {  
    public:  
      CustomEnum f()  
      {  
        return CustomEnum::Value1;  
      }  
    };  
    
  • 重载的非成员运算符 new 和运算符 delete 可能不是以内联方式声明的(默认开启等级 1 (/W1))Overloaded non-member operator new and operator delete may not be declared inline (Level 1 (/W1) on-by-default)

    当以内联方式声明非成员运算符 new 和运算符 delete 函数时,编译器的早期版本不会发出警告。Previous versions of the compiler do not issue a warning when non-member operator new and operator delete functions are declared inline. 以这种方式编写的代码格式不正确(无需诊断),并且可能由于不匹配的 new 和 delete 运算符(尤其是与调整了大小的释放共同使用时)而导致难以诊断的内存问题。Code written in this way is ill-formed (no diagnostic required) and can cause memory issues resulting from mismatched new and delete operators (especially when used together with sized deallocation) that can be difficult to diagnose. 编译器现将发出编译器警告 C4595 以帮助识别以这种方式编写的代码。The compiler now issues compiler warning C4595 to help identify code written in this way.

    warning C4595: 'operator new': non-member operator new or delete functions may not be declared inline  
    

    示例(之前)Example (before)

              inline void* operator new(size_t sz)  // warning C4595  
    {  
      ...  
    }  
    

    示例(之后)Example (after)

              void* operator new(size_t sz)  // removed inline  
    {  
      ...  
    }  
    

    修复以这种方式编写的代码可能需要将运算符定义从头文件移动到相应的源文件中。Fixing code that's written in this way might require that the operator definitions be moved out of a header file and into a corresponding source file.

更新 3 中的符合性改进Conformance Improvements in Update 3

  • 现在,std::is_convertable 可以检测自我赋值(标准库)std::is_convertable now detects self-assignment (standard library)

    以前版本的 std::is_convertable type-trait 在其复制构造函数被删除或私有时,无法正确检测类类型的自我赋值。Previous versions of the std::is_convertable type-trait did not correctly detect self-assignment of a class type when its copy constructor is deleted or private. 现在,当应用于具有已删除或私有复制构造函数的类类型时,std::is_convertable<>::value 已正确设置为 falseNow, std::is_convertable<>::value is correctly set to false when applied to a class type with a deleted or private copy constructor.

    没有与此更改相关联的编译器诊断。There is no compiler diagnostic associated with this change.

    示例Example

    #include <type_traits>  
    
    class X1  
    {  
    public:  
        X1(const X1&) = delete;  
    };  
    
    class X2  
    {  
    private:  
        X2(const X2&);  
    };  
    
    static_assert(std::is_convertible<X1&, X1>::value, "BOOM");static_assert(std::is_convertible<X2&, X2>::value, "BOOM");  
    

    在以前版本的 Visual C++ 中,此示例底部的静态断言可传递,因为 std::is_convertable<>::value 错误地设置为 trueIn previous versions of Visual C++, the static assertions at the bottom of this example pass because std::is_convertable<>::value was incorrectly set to true. 现在,std::is_convertable<>::value 正确设置为 false,使静态断言失败。Now, std::is_convertable<>::value is correctly set to false, causing the static assertions to fail.

  • 默认设置或已删除的日常复制和移动构造函数遵从访问说明符Defaulted or deleted trivial copy and move constructors respect access specifiers

    对于默认设置或已删除的日常复制和移动构造函数的访问说明符,早期版本的编译器在允许调用之前不进行检查。Previous versions of the compiler did not check the access specifier of defaulted or deleted trivial copy and move constructors before allowing them to be called. 这种旧行为不正确,也不符合 C++ 标准。This old behavior was incorrect and does not conform to the C++ standard. 在某些情况下,这种旧行为会导致无提示代码生成错误风险,从而导致不可预知的运行时行为。In some cases, this old behavior created a risk of silent bad code generation, resulting in unpredictable runtime behavior. 现在,编译器检查默认设置或已删除的日常复制和移动构造函数的访问说明符,以确定是否能调用它,如果不能,则发出编译器警告 C2248。The compiler now checks the access specifier of defaulted or deleted trivial copy and move constructors to determine whether it can be called, and if not, issues compiler warning C2248 as a result.

    error C2248: 'S::S' cannot access private member declared in class 'S'  
    

    示例(之前)Example (before)

    class S {  
    public:  
           S() = default;  
    private:  
        S(const S&) = default;  
    };  
    
    void f(S);  // pass S by value  
    
    int main()  
    {  
        S s;  
        f(s);  // error C2248, can't invoke private copy constructor  
    }  
    

    示例(之后)Example (after)

    class S {  
    public:  
           S() = default;  
    private:  
        S(const S&) = default;  
    };  
    
    void f(const S&);  // pass S by reference  
    
    int main()  
    {  
        S s;  
        f(s);  
    }  
    
  • 弃用属性化 ATL 代码支持(默认开启等级 1 (/W1))Deprecation of attributed ATL code support (Level 1 (/W1) on-by-default)

    以前版本的编译器支持属性化 ATL 代码。Previous versions of the compiler supported attributed ATL code. 由于下一阶段将删除从 Visual C++ 2008 开始的属性化 ATL 代码支持,所以已弃用属性化 ATL 代码。As the next phase of removing support for attributed ATL code that began in Visual C++ 2008, attributed ATL code has been deprecated. 编译器现将发出编译器警告 C4467 以帮助识别这类已弃用的代码。The compiler now issues compiler warning C4467 to help identify this kind of deprecated code.

    warning C4467: Usage of ATL attributes is deprecated  
    

    若要在编译器删除支持之前继续使用属性化 ATL 代码,可以通过将 /Wv:18/wd4467 命令行参数传递给编译器或在源代码中添加 #pragma warning(disable:4467) 来禁用此警告。If you want to continue using attributed ATL code until support is removed from the compiler, you can disable this warning by passing the /Wv:18 or /wd4467 command line arguments to the compiler, or by adding #pragma warning(disable:4467) in your source code.

    示例 1(之前)Example 1 (before)

              [uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")]  
    class A {};  
    

    示例 1(之后)Example 1 (after)

    __declspec(uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")) A {};  
    

    有时需要创建 IDL 文件以避免使用已弃用的 ATL 属性,如以下示例代码所示Sometimes you might need or want to create an IDL file to avoid the use deprecated ATL attributes, as in the example code below

    示例 2(之前)Example 2 (before)

    [emitidl];  
    [module(name="Foo")];  
    
    [object, local, uuid("9e66a290-4365-11d2-a997-00c04fa37ddb")]  
    __interface ICustom {  
        HRESULT Custom([in] long l, [out, retval] long *pLong);  
        [local] HRESULT CustomLocal([in] long l, [out, retval] long *pLong);  
    };  
    
    [coclass, appobject, uuid("9e66a294-4365-11d2-a997-00c04fa37ddb")]  
    class CFoo : public ICustom  
    {  
        // ...  
    };  
    

    首先,创建 *.idl 文件;vc140.idl 生成的文件可用于获取包含接口和注释的 *.idl文件。First, create the *.idl file; the vc140.idl generated file can be used to obtain an *.idl file containing the interfaces and annotations.

    然后,将 MIDL 步骤添加到生成中以确保生成 C++ 接口定义。Next, add a MIDL step to your build to make sure that the C++ interface definitions are generated.

    示例 2 IDL(之后)Example 2 IDL (after)

    import "docobj.idl";  
    
    [  
        object,local,uuid(9e66a290-4365-11d2-a997-00c04fa37ddb)  
    ]  
    
    interface ICustom : IUnknown {  
        HRESULT  Custom([in] long l, [out,retval] long *pLong);  
        [local] HRESULT  CustomLocal([in] long l, [out,retval] long *pLong);  
    };  
    
    [ version(1.0), uuid(29079a2c-5f3f-3325-99a1-3ec9c40988bb) ]  
    library Foo  
    {  
        importlib("stdole2.tlb");  
        importlib("olepro32.dll");  
            [  
                version(1.0),  
                appobject,uuid(9e66a294-4365-11d2-a997-00c04fa37ddb)  
            ]  
    
        coclass CFoo {  
            interface ICustom;  
        };  
    }  
    

    然后,在实现文件中直接使用 ATL,如以下示例代码所示。Then, use ATL directly in the implementation file, as in the example code below.

    示例 2 实现(之后)Example 2 Implementation (after)

    #include <idl.header.h>  
    #include <atlbase.h>  
    
    class ATL_NO_VTABLE CFooImpl :  
        public ICustom,  
        public ATL::CComObjectRootEx<CComMultiThreadModel>  
    {  
    public:  
        BEGIN_COM_MAP(CFooImpl)  
        COM_INTERFACE_ENTRY(ICustom)  
        END_COM_MAP()  
    };  
    
  • 预编译标头 (PCH) 文件和不匹配的 #include 指令(仅影响 /Wall /WX)Precompiled header (PCH) files and mismatched #include directives (only affects /Wall /WX)

    使用预编译标头 (PCH) 文件时,以前版本的编译器接受 -Yc-Yu 编译之间的源文件中不匹配的 #include 指令。Previous versions of the compiler accepted mismatched #include directives in source files between -Yc and -Yu compilations when using precompiled header (PCH) files. 编译器不再接受以这种方式编写的代码。Code written in this way is no longer accepted by the compiler. 使用 PCH 文件时,编译器现将发出编译器警告 CC4598 以帮助识别不匹配的 #include 指令。The compiler now issues compiler warning CC4598 to help identify mismatched #include directives when using PCH files.

    warning C4598: 'b.h': included header file specified for Ycc.h at position 2 does not match Yuc.h at that position  
    

    示例(之前):Example (before):

    X.cpp (-Ycc.h)X.cpp (-Ycc.h)

    #include "a.h"  
    #include "b.h"  
    #include "c.h"  
    

    Z.cpp (-Yuc.h)Z.cpp (-Yuc.h)

    #include "b.h"  
    #include "a.h"  // mismatched order relative to X.cpp  
    #include "c.h"  
    

    示例(之后)Example (after)

    X.cpp (-Ycc.h)X.cpp (-Ycc.h)

    #include "a.h"  
    #include "b.h"   
    #include "c.h"  
    

    Z.cpp (-Yuc.h)Z.cpp (-Yuc.h)

    #include "a.h"  
    #include "b.h" // matched order relative to X.cpp  
    #include "c.h"  
    
  • 预编译标头 (PCH) 文件和不匹配的包含目录(仅影响 /Wall /WX)Precompiled header (PCH) files and mismatched include directories (only affects /Wall /WX)

    使用预编译标头 (PCH) 文件时,对于 -Yc-Yu 编译之间的编译器,以前版本的编译器接受不匹配的包含目录 (-I) 命令行参数。Previous versions of the compiler accepted mismatched include directory (-I) command line arguments to the compiler between -Yc and -Yu compilations when using precompiled header (PCH) files. 编译器不再接受以这种方式编写的代码。Code written in this way is no longer accepted by the compiler. 使用 PCH 文件时,编译器现将发出编译器警告 CC4599 以帮助识别不匹配的包含目录 (-I) 命令行参数。The compiler now issues compiler warning CC4599 to help identify mismatched include directory (-I) command line arguments when using PCH files.

    warning C4599: '-I..' : specified for Ycc.h at position 1 does not match Yuc.h at that position  
    

    示例(之前)Example (before)

    cl /c /Wall /Ycc.h -I.. X.cpp  
    cl /c /Wall /Yuc.h Z.cpp  
    

    示例(之后)Example (after)

    cl /c /Wall /Ycc.h -I.. X.cpp  
    cl /c /Wall /Yuc.h -I.. Z.cpp