本页包括从 Visual Studio 2003 到 Visual Studio 2015 的所有 Visual C 版本的新功能页面。从早期版本提供这些信息的目的是方便用户 Visual Studio 进行升级。
Visual Studio 2015 中 C 的新增功能
在 Visual Studio 2015 在更高的版本中,编译符合性的持续改进有时会改变编译理解现有源代码的方式。当这种情况发生时,在生成过程中可能会遇到新的或不同的错误,甚至以前生成的代码也可能会出现行为差异。
幸运的是,这些差异对大部分源代码没有影响或影响极小,而且需要更改源代码或进行其他更改以解决这些差异时,修补程序通常小型且简单。我们列出了许多可接受的源代码示例(之前)及其修复程序(之后),现在可能需要更改。
虽然这些差异可能会影响源代码或其他生成项目,但它们不会影响 Visual C 版本更新之间的二进制文件兼容性。重大变更是严重性较高的变更,可能会影响二进制文件的兼容性,但这种二进制文件的兼容性中断只发生在 Visual C 主版本之间。例如,在 Visual C 2013 和 Visual C 2015 之间。有关 Visual C 2013 和 Visual C 2015 请参考重大变更的详细信息Visual C 改变历史记录(2003) - 2015)。
-
Visual Studio 2015 一致性改进
-
Visual Studio 2015 Update 1 提高符合性
-
Visual Studio 2015 Update 2 一致性改进
-
Visual Studio 2015 Update 3 提高符合性
Visual Studio 2015 提高符合性
-
/Zc:forScope-
编译选项已被废弃,并将从未来版本中删除。Command line warning D9035: option 'Zc:forScope-' has been deprecated and will be removed in a future release
此选项以前常用于允许非标准代码在点位置后使用循环变量。根据标准规范,这些变量应在范围之外。仅在使用
/Za
需要编译选项,因为如果没有,/Za
,循环结束后将始终允许使用 for 循环变量。如果您不关心标准符合性(例如,如果您的代码不移植到其他编译器),请关闭它/Za
选项(或将禁用语言扩展属性设置为否)。如果您真的关心编写可移植和符合标准的代码,您应该重写代码,以便将此类变量的声明转移到循环以外的点,使其符合标准。// 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
编译选项(生成函数原型)不再可用。该编译器选项已被弃用。 -
你不能再用了 mstest.exe 从命令中运行 C /CLI 单元测试。请改用 vstest.console.exe
-
在以前没有错误的情况下,不允许存储类说明符。现在编译器报告错误了 C2071(非法存储)。根据标准,可变说明符只能用于类数据成员的名称,不能用于声明 const 或 static 该名称不能应用于参考成员。
例如,考虑以下代码:
struct S { mutable int &r; };
早期版本的 Microsoft C 编译器接受此代码,但编译器现在将报告以下错误:
error C2071: 'S::r': illegal storage class
要修复这个错误,只需删除多余的错误关键字。
-
不能再用了或作为 typedef 别名,因为这些类型现在被视为内置。经常会有用户和库作者和分别定义为
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; }
如需更新代码,请删除声明并重命名任何其他与这些名称冲突的标志符。
-
在提供显式模板参数时,将准确检查包含非类型模板参数的某些代码的类型符合性。例如,早期版本 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):
error C2893: Failed to specialize function template 'void S2::f(void)'note: With the following template arguments:note: 'C=S1'note: 'Function=S1::f'
如果您想在代码中修复此错误,请确保您使用的模板自变量类型与模板参数声明的类型相匹配。
-
编译器不再接受函数上的函数
__declspec(align)
。过去总是忽略这一点,但现在编译致编译错误。error C3323: 'alignas' and '__declspec(align)' are not allowed on function declarations
要解决这个问题,请从函数声明中删除
__declspec(align)
。因为它不起作用,删除它不会改变任何内容。 -
异常处理有几个变化。首先,异常对象必须可以复制或移动。下列代码在 Visual Studio 2013 编译,但不是 Visual Studio 2015 中间编译:
struct S { public: S(); private: S(const S &); }; int main() { throw S(); // error }
问题在于,复制构造函数是私有的,因此对象无法像处理异常的标准过程那样进行复制。 声明复制构造函数时,也是如此 。
struct S { S(); explicit S(const S &); }; int main() { throw S(); // error }
若要更新你的代码,请确保异常对象的复制构造函数是公用的且未标记为 。
通过值捕获异常还要求异常对象可复制。 下列代码在 Visual Studio 2013 中进行编译,但不在 Visual Studio 2015 中进行编译:
struct B { public: B(); private: B(const B &); }; struct D : public B { }; int main() { try { } catch (D d) // error { } }
可以通过将的参数类型更改为引用来解决此问题 。
catch(D& d) { }
-
编译器现在支持用户定义的文本。 因此,宏之前没有任何干预空格的字符串文本被视为用户定义的文本,这可能会产生错误或意外结果。 例如,在早期的编译器中,成功编译了以下代码:
#define _x "there" char* func() { return "hello"_x; } int main() { char * p = func(); return 0; }
编译器将此视为后跟宏的字符串文本“hello”,该宏是展开的“there”,然后两个字符串串联成一个。 在 Visual Studio 2015 中,编译器将其解释为用户定义的文本,但由于未定义所匹配的用户定义文本 _x,它将报告错误。
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?
若要解决此问题,请在字符串文本和宏之间添加一个空格。
-
与上文类似,由于字符串分析中的相关变化,没有任何空格的相邻字符串文本(或宽或窄的字符字符串文本)被视为 Visaul C++ 早期版本中的单个串联字符串。 在 Visual Studio 2015 中,现必须在两个字符串之间添加空格。 例如,必须更改以下代码:
char * str = "abc""def";
只需在两个字符串之间添加空间。
char * str = "abc" "def";
-
已对操作员做出更改 ,使其符合 c + + 14 标准。 标准更改的详细信息位于 C++ 调整了大小的释放。 更改将添加 采用大小参数的全局运算符的形式。 重大更改是,如果你以前使用 同一签名 (的运算符与 运算符) 相对应,你将收到 (C2956 的编译器错误,该错误发生在使用 运算符的位置,因为这是代码中编译器尝试标识适当匹配 运算符) 的位置。
函数
void operator delete(void *, size_t)
是与 C++11 中的 placement new 函数void * operator new(size_t, size_t)
对应的 placement delete 运算符。 使用 c + + 14 大小的释放,此 函数现在是 (全局运算符) 的 常用释放函数 。 标准要求,如果使用 "放置" 功能,则 " " 查找相应的 函数并找到常用的释放函数,则程序格式不正确。例如,假设你的代码同时定义了一个 的和一个 :
void * operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
出现此问题的原因是你定义的 运算符与新的全局大小运算符之间的函数签名匹配 。 考虑是否可以使用不同于
size_t
任何 和运算符的其他类型 。 请注意,的类型与size_t
编译器相关; 它是 Visual C++ 中的。 较好的解决办法就是使用如下的枚举类型:enum class my_type : size_t {};
然后,更改放置的定义 ,并 使用此类型作为第二个参数,而不是
size_t
。 还需要更新对 的调用以传递新类型 (例如,通过使用static_cast<my_type>
从整数值转换) 并更新和的定义 以强制转换回整数类型。 不需要使用 来实现此操作; 包含成员的类类型size_t
也将起作用。另一种解决方法是,可以完全消除新的 。 如果你的代码使用 实现内存池,其中位置参数是要分配或删除的对象的大小,则调整了大小的释放功能可能适合替换你自己的自定义内存池代码,并且你可以删除位置函数,仅使用自己的双参数 运算符而不是放置函数。
如果你不想立即更新代码,可通过编译器选项
/Zc:sizedDealloc-
还原到之前的行为。 如果使用此选项,则两参数 函数不存在,因此不会导致与您的 运算符发生冲突。 -
联合数据成员不再具有引用类型。 下列代码在 Visual Studio 2013 中成功编译,但在 Visual Studio 2015 中引发错误。
union U1 { const int i; }; union U2 { int &i; }; union U3 { struct {int &i;}; };
前面的代码产生以下错误:
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
若要解决此问题,请将引用类型更改为指针或值。 更改指针类型需要对使用联合字段的代码进行更改。 将代码更改为值将更改存储在联合中的数据,这会影响其他字段,因为联合类型中的字段共享相同的内存。 根据值的大小,它还可能更改联合的大小。
-
现在更符合标准。 早期版本的编译器生成了匿名联合的显式构造函数和析构函数。 Visual Studio 2015 中已将其删除。
struct S { S(); }; union { struct { S s; }; } u; // C2280
前述代码在 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
若要解决此问题,请提供你对构造函数和/或析构函数的定义。
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; }
在 Visual Studio 2013 中,创建联合时会调用 S 的构造函数,清理函数 f 的堆栈时会调用 S 的析构函数。 但在 Visual Studio 2015 中,不调用构造函数和析构函数。 编译器会对关于此行为的更改发出警告。
warning C4587: 'U::s': behavior change: constructor is no longer implicitly calledwarning C4588: 'U::s': behavior change: destructor is no longer implicitly called
若要还原原始行为,请赋予匿名结构一个名称。 无论编译器版本为何,非匿名结构的运行时行为都是相同的。
#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; }
-
对模板的名称解析进行了更改。 在 C++ 中,考虑名称解析的候选对象时,可能会出现作为潜在匹配项考虑的一个或多个名称生成无效的模板实例化的情况。 这些无效的实例化通常不会导致编译器错误,这被称为 SFINAE(替换失败不是错误)原则。
现在,如果 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): 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”。
在这种情况下,解决方法是在定义类之前,不使用此类类型特征。 如果将 D 和 B 的定义移到代码文件的开头,错误将得到解决。 如果定义位于标头文件中,请检查标头文件的 include 语句的顺序,以确保在使用有问题的模板之前,对任何类定义进行了编译。
-
在 Visual Studio 2013 和 Visual Studio 2015 中,如果某个类具有用户定义的移动构造函数,但没有用户定义的复制构造函数,则编译器生成该类的复制构造函数。 在 Dev14 中,此隐式生成的复制构造函数也标记为“= delete”。
Visual Studio 2015 Update 1 中的一致性改进
-
早期版本的编译器允许派生类调用 间接派生 private virtual
基类的成员函数。 这种旧行为不正确,也不符合 C++ 标准。 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2280。
error C2280: 'void *S3::__delDtor(unsigned int)': attempting to reference a deleted function
示例(之前)
class base
{
protected:
base();
~base();
};
class middle: private virtual base {};class top: public virtual middle {};
void destroy(top *p)
{
delete p; //
}
示例(之后)
class base; // as above
class middle: protected virtual base {};
class top: public virtual middle {};
void destroy(top *p)
{
delete p;
}
- 或 -
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 运算符声明为静态,并在全局命名空间之外的命名空间中声明。 这种旧行为会导致程序无法调用程序员所需的 或 运算符实现,从而导致无提示的运行时行为。 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2323。
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.
示例(之前)
static inline void * __cdecl operator new(size_t cb, const std::nothrow_t&) // error C2323
示例(之后)
void * __cdecl operator new(size_t cb, const std::nothrow_t&) // removed 'static inline'
此外,尽管编译器不能进行具体诊断,但内联运算符 new 会被视为格式不正确。
-
早期版本的编译器允许以无提示忽略的方式对非类类型调用“operator type()”。 这种旧行为会导致无提示代码生成错误风险,从而导致不可预知的运行时行为。 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C2228。
error C2228: left of '.operator type' must have class/struct/union
示例(之前)
typedef int index_t;
void bounds_check(index_t index);
void login(int column)
{
bounds_check(column.operator index_t()); // error C2228
}
示例(之后)
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'
}
-
早期版本的编译器允许 在详细的类型说明符中使用; 以这种方式编写的代码在语义上不正确。 编译器不再接受这种方式编写的代码,因此会发出编译器错误 C3406。
error C3406: 'typename' cannot be used in an elaborated type specifier
示例(之前)
template <typename class T>
class container;
示例(之后)
template <class T> // alternatively, could be 'template <typename T>'; 'typename' is not elaborating a type specifier in this case
class container;
-
早期版本的编译器不支持对初始值设定项列表中的数组进行类型推断。 编译器现在支持这种形式的类型推断,因此调用使用初始值设定项列表的函数模板现在可能会不明确,或者选择一个与以前版本的编译器不同的重载。 要解决这些问题,程序现在必须显式指定程序员所需的重载。
当这一新行为导致重载解决方法要考虑与以往候选一样好的其他候选时,调用变得不明确,编译器会发出编译器错误 C2668。
error C2668: 'function' : ambiguous call to overloaded function.
示例 1: 对重载函数的调用不明确(之前)
// 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: 对重载函数的调用不明确(之后)
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);
}
这一新行为会导致重载解决方法要考虑比以往候选更适合的其他候选时,调用将明确地解析为新候选,导致程序行为的更改可能与程序员的需要有所不同。
示例 2:重载解决方法的更改(之前)
// 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:重载解决方法的更改(之后)
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 语句的最后一行发出。 因此,现在发出这些警告的行与过去不同,按照需要使用 #pragma warning(disable:####)
可不再禁止显示以前禁止显示的警告。 要按照需要禁止显示这些警告,可能需要将 #pragma warning(disable:####)
指令移到第一个可能有问题的用例上面的行。 以下是还原的警告。
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 示例(之前)
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 示例(之后)
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;
}
};
在其文档中提供了其他还原警告的示例。
-
#include:在路径名中使用父目录说明符“..”(只影响 /Wall
/WX
)
早期版本的编译器没有检测到使用父目录说明符“..” (在 #include
指令的路径名中)。 以这种方式编写的代码通常用于包含因不正确使用项目相对路径而留在项目外的标头。 这一旧行为会引发风险,导致编译程序时包含了程序员不需要的源文件来,或这些相对路径不能移植到其他生成环境中。 编译器现在会检测以这种方式编写的代码并通知程序员,并发出可选编译器警告 C4464(如果已启用)。
warning C4464: relative include path contains '..'
示例(之前)
#include "..\headers\C4426.h" // emits warning C4464
示例(之后)
#include "C4426.h" // add absolute path to 'headers\' to your project's include directories
此外,虽然编译器并不会进行具体诊断,但建议不应将父目录说明符“..”用于指定项目的包含目录。
-
#pragma optimize() 超出标头文件的末尾(只影响 /Wall
/WX
)
早期版本的编译器无法检测到对转义翻译单元中包含的标头文件的优化标志设置的更改。 编译器现在会检测以这种方式编写的代码并通知程序员,并在有问题的 #include
的位置发出可选编译器警告 C4426(如果已启用)。 只有更改与编译器命令行参数设置的优化标志发生冲突时,才发出此警告。
warning C4426: optimization flags changed after including header, may be due to #pragma optimize()
示例(之前)
// C4426.h
#pragma optimize("g", off)
...
// C4426.h ends
// C4426.cpp
#include "C4426.h" // warning C4426
示例(之后)
// 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
)
早期版本的编译器无法检测到不同源文件中与 #pragma warning(pop)
状态更改配对的 #pragma warning(push)
状态更改,这并不是我们所预期的。 这种旧行为会引发风险,导致程序编译时会启用一组程序员不希望出现的警告,可能会导致无提示的运行时行为错误。 编译器现在能够检测以这种方式编写的代码并通知程序员,并在匹配 #pragma warning(pop)
位置发出可选编译器警告 C5031(如果已启用)。 此警告中有一个注释,其中引用了相应 #pragma warning(push)
的位置。
warning C5031: #pragma warning(pop): likely mismatch, popping warning state pushed in different file
示例(之前)
// 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
...
示例(之后)
// 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"
...
虽然不常见,但是有时会故意以这种方式编写代码。 以这种方式编写的代码对于 #include
顺序的更改比较敏感;如果可能,我们建议源代码文件以自包含的方式管理警告状态。
-
#pragma warning(push) 不匹配(只影响 /Wall
/WX
)
早期版本的编译器无法检测到翻译单元末尾出现的不匹配 #pragma warning(push)
状态更改。 而现在,编译器可检测按此方式编写的代码并通知程序员,还可在 #pragma warning(push)
不匹配的位置发出编译器警告 C5032(如已启用)。 只有翻译单元中没有任何编译错误时,才会发出此警告。
warning C5032: detected #pragma warning(push) with no corresponding #pragma warning(pop)
示例(之前)
// 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
示例(之后)
// 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 warning
状态更改,因而无法发出所有所需的警告。 这种行为会引发风险,导致在程序不希望的情况下有效禁止显示某些警告。 而现在,编译器可更加可靠地跟踪 #pragma warning
状态(尤其与模板内部的 #pragma warning
状态更改相关),并选择性发出新警告 C5031 和 C5032,目的是帮助程序员了解意外使用 #pragma warning(push)
和 #pragma warning(pop)
的情况。
由于 #pragma warning
状态更改跟踪得到了改进,现在可能会发出之前误禁的警告或与之前误诊问题相关的警告。
-
针对早期版本的编译器进行的 C++ 标准库的更改和内联函数调用能力改进可能会使编译器能够证明某些代码现在无法访问。 这一新行为可能导致新警告并更频繁地发出警告 C4720 实例。
warning C4720: unreachable code
在许多情况下,只有启用优化进行编译时,才会发出此警告,因为优化可能嵌入更多函数调用,消除冗余代码或者能够确定某些代码是否无法访问。 我们发现,C4720 的新实例在 块中经常发生,尤其是在使用 std:: find的情况下。
示例(之前)
try
{
auto iter = std::find(v.begin(), v.end(), 5);
}
catch(...)
{
do_something(); // ok
}
示例(之后)
try
{
auto iter = std::find(v.begin(), v.end(), 5);
}
catch(...)
{
do_something(); // warning C4702: unreachable code
}
Visual Studio 2015 Update 2 的符合性改进
-
由于缺少对表达式 SFINAE 的支持,早期版本的编译器不会分析说明符内的某些类型的表达式。 这种旧行为不正确,也不符合 C++ 标准。 由于持续的符合性改进,此编译器现已可分析这些表达式,并能为表达式 SFINAE 提供部分支持。 因此,此编译器现在可发出在编译器的早期版本无法分析的表达式中找到的警告和错误。
此新行为分析包含尚未 声明的类型的表达式时,编译器会发出编译器错误 C2039。
error C2039: 'type': is not a member of '`global namespace''
示例 1:使用未声明的类型(之前)
struct s1
{
template <typename T>
auto f() -> decltype(s2<T>::type::f()); // error C2039
template<typename>
struct s2 {};
}
示例 1(之后)
struct s1
{
template <typename> // forward declare s2struct s2;
template <typename T>
auto f() -> decltype(s2<T>::type::f());
template<typename>
struct s2 {};
}
当这一新行为分析的 表达式缺少必要使用 关键字来指定依赖名称是类型时,编译器会发出编译器警告 C4346 以及编译器错误 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:依赖名称不是类型(之前)
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(之后)
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>()));
};
-
编译器的早期版本允许具有成员变量的类 自动生成默认复制/移动构造函数和默认复制/移动赋值运算符。 这种旧行为不正确,也不符合 C++ 标准。 编译器现在认为拥有可变成员变量的类具有非常用构造函数和赋值运算符,这将防止自动生成这些运算符的默认实现。 当此类为某一联合(或类中的匿名联合)的成员时,会将联合(或包含匿名联合的类)的复制/移动构造函数和复制/移动赋值运算符的隐式定义为已删除。 如果尝试构造或复制联合(或包含匿名联合的类)而不显示定义它们是错误的,将导致编译器发出编译器错误 C2280。
Output复制
error C2280: 'B::B(const B &)': attempting to reference a deleted function
示例(之前)
C++复制
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
示例(之后)
C++复制
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
-
Visual C++ 2015 的早期版本允许静态成员函数具有 cv 限定符。 此行为是由于 Visual C++ 2015 和 Visual C++ 2015 Update 1 中的回归而导致的;Visual C++ 2013 和 Visual C++ 的早期版本拒绝接受以这种方式编写的代码。 Visual C++ 2015 和 Visual C++ 2015 Update 1 的行为不正确且不符合 C++ 标准。 Visual Studio 2015 Update 2 拒绝接受以这种方式编写的代码,并改为发出编译器错误 C2511。
Output复制
error C2511: 'void A::func(void) const': overloaded member function not found in 'A'
示例(之前)
C++复制
struct A
{
static void func();
};
void A::func() const {} // C2511
示例(之后)
C++复制
struct A
{
static void func();
};
void A::func() {} // removed const
-
(仅影响 /ZW
)
为 Windows 运行时 (WinRT) 编译的代码不允许 前向声明类型,这与使用编译器开关为 .Net Framework 编译托管 c + + 代码时相似 /clr
。 此行为可确保枚举大小始终为已知,并可将其正确映射到 WinRT 类型系统。 编译器将拒绝接受以这种方式编写的代码,并发出编译器错误 C2599 和编译器错误 C3197。
Output复制
error C2599: 'CustomEnum': the forward declaration of a WinRT enum is not allowed
Output复制
error C3197: 'public': can only be used in definitions
示例(之前)
C++复制
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;
}
};
示例(之后)
C++复制
// forward declaration of CustomEnum removed
namespace A {
public enum class CustomEnum: int32
{
Value1
};
}
public ref class Component sealed
{
public:
CustomEnum f()
{
return CustomEnum::Value1;
}
};
-
(默认开启等级 1 (/W1
))
当以内联方式声明非成员运算符 new 和运算符 delete 函数时,编译器的早期版本不会发出警告。 按方式编写的代码格式有误(无需诊断),并且可能由于不匹配的 new 和 delete 运算符(尤其是与调整了大小的释放共同使用时)而导致难以诊断的内存问题。 编译器现将发出编译器警告 C4595 以帮助识别以这种方式编写的代码。
Output复制
warning C4595: 'operator new': non-member operator new or delete functions may not be declared inline
示例(之前)
C++复制
inline void* operator new(size_t sz) // warning C4595
{
...
}
示例(之后)
C++复制
void* operator new(size_t sz) // removed inline
{
...
}
修复以这种方式编写的代码可能需要将运算符定义从头文件移动到相应的源文件中。
Visual Studio 2015 Update 3 中的一致性改进
-
(标准库) 以前版本的 std::is_convertable
type-trait 在其复制构造函数被删除或私有时,无法正确检测类类型的自我赋值。 现在, std::is_convertable<>::value
在应用于具有已删除或私有复制构造函数的类类型时,正确设置为。
没有与此更改相关联的编译器诊断。
示例
C++复制
#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
错误地设置为 。 现在, std::is_convertable<>::value
正确设置为 ,导致静态断言失败。
-
对于默认设置或已删除的日常复制和移动构造函数的访问说明符,早期版本的编译器在允许调用之前不进行检查。 这种旧行为不正确,也不符合 C++ 标准。 在某些情况下,这种旧行为会导致无提示代码生成错误风险,从而导致不可预知的运行时行为。 现在,编译器检查默认设置或已删除的日常复制和移动构造函数的访问说明符,以确定是否能调用它,如果不能,则发出编译器警告 C2248。
Output复制
error C2248: 'S::S' cannot access private member declared in class 'S'
示例(之前)
C++复制
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
}
示例(之后)
C++复制
class S {
public:
S() = default;
private:
S(const S&) = default;
};
void f(const S&); // pass S by reference
int main()
{
S s;
f(s);
}
-
(默认开启等级 1 (/W1
))
以前版本的编译器支持属性化 ATL 代码。 由于下一阶段将删除从 Visual C++ 2008 开始的属性化 ATL 代码支持,所以已弃用属性化 ATL 代码。 编译器现将发出编译器警告 C4467 以帮助识别这类已弃用的代码。
Output复制
warning C4467: Usage of ATL attributes is deprecated
若要在编译器删除支持之前继续使用属性化 ATL 代码,可以通过将 /Wv:18
或 /wd4467
命令行参数传递给编译器或在源代码中添加 #pragma warning(disable:4467)
来禁用此警告。
示例 1(之前)
C++复制
[uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")]
class A {};
示例 1(之后)
C++复制
__declspec(uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")) A {};
有时需要创建 IDL 文件以避免使用已弃用的 ATL 属性,如以下示例代码所示
示例 2(之前)
C++复制
[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文件。
然后,将 MIDL 步骤添加到生成中以确保生成 C++ 接口定义。
示例 2 IDL(之后)
C++复制
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,如以下示例代码所示。
示例 2 实现(之后)
C++复制
#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()
};
-
(仅影响 /Wall
/WX
)
使用预编译标头 (PCH) 文件时,以前版本的编译器接受 -Yc
和 -Yu
编译之间的源文件中不匹配的 #include
指令。 编译器不再接受以这种方式编写的代码。 使用 PCH 文件时,编译器现将发出编译器警告 CC4598 以帮助识别不匹配的 #include
指令。
Output复制
warning C4598: 'b.h': included header file specified for Ycc.h at position 2 does not match Yuc.h at that position
示例(之前):
X.cpp (-Ycc.h)
C++复制
#include "a.h"
#include "b.h"
#include "c.h"
Z.cpp (-Yuc.h)
C++复制
#include "b.h"
#include "a.h" // mismatched order relative to X.cpp
#include "c.h"
示例(之后)
X.cpp (-Ycc.h)
C++复制
#include "a.h"
#include "b.h"
#include "c.h"
Z.cpp (-Yuc.h)
C++复制
#include "a.h"
#include "b.h" // matched order relative to X.cpp
#include "c.h"
-
(仅影响 /Wall
/WX
)
使用预编译标头 (PCH) 文件时,对于 -Yc
和 -Yu
编译之间的编译器,以前版本的编译器接受不匹配的包含目录 (-I
) 命令行参数。 编译器不再接受以这种方式编写的代码。 使用 PCH 文件时,编译器现将发出编译器警告 CC4599 以帮助识别不匹配的包含目录 (-I
) 命令行参数。
Output复制
warning C4599: '-I..' : specified for Ycc.h at position 1 does not match Yuc.h at that position
示例(之前)
ms-dos复制
cl /c /Wall /Ycc.h -I.. X.cpp
cl /c /Wall /Yuc.h Z.cpp
示例(之后)
ms-dos复制
cl /c /Wall /Ycc.h -I.. X.cpp
cl /c /Wall /Yuc.h -I.. Z.cpp
Visual Studio 2013 中 C++ 的新增功能
改进的 ISO C/C++ 标准支持
编译器
MSVC 支持以下 ISO C++11 语言功能:
- 函数模板的默认模板参数。
- 委托构造函数
- 显式转换运算符。
- 初始值设定项列表和统一初始化。
- 原始字符串文本。
- 可变参数模板。
- 别名模板。
- 已删除的函数。
- 非静态数据成员初始值设定项 (NSDMI)。
- 默认的函数。 *
- 支持以下 ISO C99 语言功能:
- _Bool
- 复合文本。
- 指定的初始值设定项。
- 组合带有代码的声明。
- 字符串文本转换为可修改的值可通过使用新编译器选项
/Zc:strictStrings
禁用。 在 c + + 98 中,已弃用从字符串文本到 (和宽字符串) 文本的转换 wchar_t*
。 在 C++11 中,已将转换完全移除。 虽然编译器可以严格遵循该标准,但提供了 /Zc:strictStrings
选项,以便您控制转换。 默认情况下,该选项是关闭的。 注意,当您在调试模式下使用此选项,STL 将无法编译。
- rvalue/lvalue 引用转换。 通过 rvalue 引用,C++11 可清晰地区分 lvalue 和 rvalue。 过去,在特定强制转换方案中,编译器不提供此功能。 已添加新编译器选项(
/Zc:rvalueCast
),以使编译器与 C++ 语言的工作文件相符(请参阅第 5.4 节 [expr.cast]/1)。 未指定选项时,该默认行为与 Visual Studio 2012 中的相同。
备注
对于默认函数,不支持使用 =default 请求逐一移动成员的构造函数和移动赋值运算符。
C99 库
为下列标头中缺少的函数添加了声明和实现:math.h、ctype.h、wctype.h、stdio.h、stdlib.h 和 wchar.h。 同样添加的还有新标头 complex.h、stdbool.h、fenv.h 和 inttypes.h,以及这些新标头中声明的所有函数的实现。 新增了一些 C++ 包装器标头(ccomplex、cfenv、cinttypes 和 ctgmath)并更新了许多其他标头(ccomplex、cctype、clocale、cmath、cstdint、cstdio、cstring、cwchar 和 cwctype)。
标准模板库
支持 C++11 显式转换运算符、初始值设定项列表、范围枚举和 variadic 模板。 现在所有容器都支持 C++11 细化的元素要求。 支持这些 C++14 功能:
- “透明运算符函子”less<>、greater<>、plus<>、multiplies<> 等等。
- make_unique<T>(args...) 和 make_unique <T[]>(n)
- cbegin()/cend()、rbegin()/rend() 和 crbegin()/crend() 非成员函数。
- <atomic> 接收了许多性能增强功能。
- <type_traits> 收到重大的稳定和代码修补程序。
重大更改
对 ISO C/C++ 标准的改进支持可能需要对现有代码进行更改,从而符合 C++11 并在 Visual Studio 2013 的 Visual C++ 中正确编译。
Visual C++ 库增强功能
- 添加了 C++ REST SDK。 它具有 REST 服务的现代 C++ 实现。
- C++ AMP 纹理支持已改进。 现在包括对 mipmap 和新采样模式的支持。
- PPL 任务支持多个计划技术和异步调试。 采用新 API,可为常规结果和异常条件创建 PPL 任务。
C++ 应用程序性能
- 自动向量化现在可以识别和优化更多 C++ 模式,加快代码运行速度。
- ARM 平台和 Atom 微型体系结构代码质量增强功能。
- 添加了 __vectorcall 调用约定。 使用 __vectorcall 调用约定来传递向量类型参数,从而使用向量寄存器。
- 新链接器选项。 使用
/Gw
(编译器)和 /Gy
(汇编)开关,使链接器优化生成精简二进制文件。
- C++ AMP 共享内存支持,可减少或消除 CPU 和 GPU 间的数据复制。
按配置优化选项 (PGO) 增强
- 通过使用 PGO 实现已优化的应用程序工作集的缩减,从而提高性能。
- 用于 Windows 运行时应用开发的新 PGO。
Windows 运行时应用开发支持
-
现在可以使用可以为空的字段(例如,而不是)来定义值类型 IBox<int>^
。 这意味着字段可以具有值,也可以等于 。
-
C++/CX 支持能够在整个应用程序二进制接口 (ABI) 中获取和传播各种异常信息的新 Windows 错误模型;这包括调用堆栈和自定义消息字符串。
-
现在可以重写用户定义的 Windows 运行时引用类型中的 ToString。
-
公共 Windows 运行时 API 现在可标记为已弃用并可收到一条自定义消息,此消息显示为生成警告并可提供迁移指南。
-
支持本机/JavaScript 互操作调试、Windows 运行时异常诊断和异步代码调试(windows 运行时和 PPL)。
备注
除本节中介绍的 C++ 特定功能和增强功能外,Visual Studio 中的其他增强功能还可帮助你编写更好的 Windows 运行时应用。
诊断增强功能
- 调试器改进。 支持异步调试和“仅我的代码”调试。
- 代码分析类别。 现在可以查看代码分析器的分类输出,帮助您找到并修复代码缺陷。
- XAML 诊断。 现在可以诊断 XAML 中的 UI 响应和电池使用情况问题。
- 图像和 GPU 调试改进。
- 在实际设备上远程捕获和重放。
- 同步 C++ AMP 和 CPU 调试。
- 改进的 C++ AMP 运行时诊断。
- HLSL 计算着色器跟踪调试。
三维图形增强功能
- 图像内容管线支持预乘 alpha DDS 格式。
- 图像编辑器使用内部预乘 alpha 进行呈现,从而避免呈现暗的光晕等项目。
- 图像和模型编辑器。 图像编辑器和模型编辑器的“着色器设计器”现在支持用户定义的筛选器创建。
IDE 与工作效率
。 您可以将多个格式设置应用于 C++ 代码。 使用这些设置,您可以控制大括号和关键字、缩进、间距和自动换行的新行位置。 当完成语句和块并且将代码粘贴到文件中时,代码将自动进行格式化。
现在,C++ 代码会自动完成对应于这些开始字符的结束字符:
- {(大括号)
- [(方括号)
- ((括号)
- '(单引号)
- "(双引号)
- 添加用于类类型的分号。
- 完成对原始字符串文本使用括号。
- (/ * /) 完成多行注释 *
在后台引用显示出文本匹配列表后自动对其进行解析和筛选。
无法访问的成员已从 IntelliSense 成员列表中筛选出来。 例如,私有成员不会在成员列表中显示,除非您修改了实现此类型的代码。 打开成员列表时,可以按 + 删除筛选 (仅适用于当前成员列表窗口) 。 您可以再次按 + 删除文本筛选并显示每个成员。
参数帮助工具提示中显示的函数签名现在将根据实际输入参数的数量而改变,而不是只显示一个随机的签名且不根据当前上下文更新。 函数显示在嵌套函数上时,参数也会适当地帮助函数。
现在,通过使用快捷菜单或键盘快捷方式上的命令,可以在标题及其相应代码文件之间切换。
在键入代码向 C++/CX 或 C++/CLI