Visual C++ 中的重大更改
新版本中会引起这类问题的更改称为重大更改,通常,修改 C++ 语言标准、函数签名或内存中的对象布局时需要进行这种更改。
跨 DLL 边界传递 CRT 对象时可能的错误。
ABI 边界处的可移植性(现代 C++)。
术语“旧行为”和“之前”指 Visual Studio 2013 和早期版本。
-
编译器的重大更改
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_CRT
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_STL
-
b38385a9-a483-4de9-99a6-797488bc5110#BK_MFC
-
/Zc:forScope- 选项
/Zc:forScope- 已弃用,并且将在将来版本中删除。
Command line warning D9035: option 'Zc:forScope-' has been deprecated and will be removed in a future release
如果你确实关心编写可移植且符合标准的代码,则应重写代码,以便通过将此类变量的声明移到循环以外的点使其符合标准。
// zc_forScope.cpp // compile with: /Zc:forScope- /Za // C2065 expected int main() { // Uncomment the following line to resolve. // int i; for (int i =0; i < 1; i++) ; i = 20; // i has already gone out of scope under /Za }
-
/Zg 编译器选项
此此编译器选项已被弃用。
-
VSTest.Console.exe 命令行选项。
-
可变关键字
根据标准,可变说明符仅可应用于类数据成员的名称,不能应用于声明为 const 或 static 的名称,也不能应用于引用成员。
例如,考虑以下代码:
struct S { mutable int &r; };
早期版本的 Visual C++ 编译器接受此代码,但现在编译器则报告以下错误:
错误 C2071: 'S::r':非法存储类
若要修复此错误,只需删除冗余的可变关键字。
-
char_16_t 和 char32_t
用户和库作者通常会将 char16_t 和 char32_t 分别定义为 uint16_t 和 uint32_t 的别名。
#include <cstdint> typedef uint16_t char16_t; //C2628 typedef uint32_t char32_t; //C2628 int main(int argc, char* argv[]) { uint16_t x = 1; uint32_t y = 2; char16_t a = x; char32_t b = y; return 0; }
若要更新你的代码,请删除 typedef 声明,并重命名与这些名称发生冲突的任何其他标识符。
-
非类型模板参数
例如,在早期版本的 Visual C++ 中正确编译的以下代码。
struct S1 { void f(int); void f(int, int); }; struct S2 { template <class C, void (C::*Function)(int) const> void f() {} }; void f() { S2 s2; s2.f<S1, &S1::f>(); }
当前编译器可以准确报告错误,因为模板参数类型不匹配模板参数(该参数是指向 const 成员的指针,但函数为非 const):
错误 C2893:未能特殊化函数模板“void S2::f(void)” 备注:使用以下模板参数: 备注:“C=S1” 备注:“Function=S1::f”
若要在代码中修复此错误,请确保你使用的模板参数类型匹配模板参数声明的类型。
-
__declspec(align)
以前会始终忽略此项,但现在会产生编译器错误。
error C3323: 'alignas' and '__declspec(align)' are not allowed on function declarations
因为它不起作用,将其删除不会更改任何内容。
-
异常处理
在 Visual Studio 2013 中的 Visual C++ 中编译的以下代码却不能在 Visual Studio 2015 中的 Visual C++ 中进行编译:
struct S { public: S(); private: S(const S &); }; int main() { throw S(); // error }
当复制构造函数为声明的 explicit 时,这同样适用。
struct S { S(); explicit S(const S &); }; int main() { throw S(); // error }
若要更新你的代码,请确保异常对象的复制构造函数是公用的且未标记为 explicit。
在 Visual Studio 2013 中的 Visual C++ 中编译的以下代码却不能在 Visual Studio 2015 中的 Visual C++ 中进行编译:
struct B { public: B(); private: B(const B &); }; struct D : public B { }; int main() { try { } catch (D d) // error { } }
可以通过将 catch 的参数类型更改为引用来解决此问题。
catch(D& d) { }
-
后面是宏的字符串文本
例如,在早期的编译器中,成功编译了以下代码:
在 Visual Studio 2015 中的 Visual C++ 中,编译器将此视为用户定义的文字,但由于没有定义匹配的用户定义的 _x 文本,它将报告错误。
若要解决此问题,请在字符串文本和宏之间添加一个空格。
-
相邻字符串文本
例如,必须更改以下代码:
只需在两个字符串之间添加空间。
-
placement new 和 placement delete
重大更改为,如果你之前使用的是具有相同签名的运算符 delete(以与 placement new 运算符对应),你将收到编译器错误(C2956,在使用 placement new 的点位置出现,因为在代码中的该位置,编译器会尝试标识适当匹配的 delete 运算符)。
标准要求为,如果使用 placement new 查找相应的 delete 函数和常用释放函数,则程序会出现格式错误。
例如,假设你的代码同时定义了 placement new 和 placement delete:
void * operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
较好的解决办法就是使用如下的枚举类型:
enum class my_type : size_t {};
你无需为此使用枚举;具有 size_t 成员的类类型也将起作用。
如果你的代码使用 placement new 实现内存池,其中位置参数是分配或删除的对象的大小,则调整了大小的释放功能可能适合替换你自定义的内存池代码,且你可以去掉位置函数,仅使用自己两个参数的 delete 运算符(而不是位置参数)。
如果使用此选项,则不存在两个参数的 delete 函数,并且也不会导致与 placement delete 运算符发生冲突。
-
联合数据成员
以下代码在 Visual Studio 2013 中的 Visual C++中成功编译,但在 Visual Studio 2015 中的 Visual C++ 中产生错误。
union U1 { const int i; }; union U2 { int &i; }; union U3 { struct {int &i;}; };
前面的代码产生以下错误:
test.cpp(67):错误 C2625:U2::i:非法的联合成员;类型“int &”为引用类型 test.cpp(70):错误 C2625:U3::i:非法的联合成员;类型“int &”为引用类型
根据值的大小,它还可能更改联合的大小。
-
这些在 Visual Studio 2015 中的 Visual C++ 中已删除。
struct S { S(); }; union { struct { S s; }; } u; // C2280
前面的代码在 Visual Studio 2015 中的 Visual C++ 中生成以下错误:
error C2280: '<unnamed-type-u>::<unnamed-type-u>(void)': attempting to reference a deleted function note: compiler has generated '<unnamed-type-u>::<unnamed-type-u>' here
若要解决此问题,请提供你对构造函数和/或析构函数的定义。
struct S { // Provide a default constructor by adding an empty function body. S() {} }; union { struct { S s; }; } u;
-
具有匿名结构的联合
请考虑以下代码,其中联合 U 包含一个匿名结构,此匿名结构包含的成员是一个具有析构函数的命名结构 S。
#include <stdio.h> struct S { S() { printf("Creating S "); } ~S(){ printf("Destroying S "); } }; union U { struct { S s; }; U() {} ~U(){} }; void f() { U u; // Destructor implicitly called here. } int main() { f(); char s[1024]; printf("Press any key. "); gets_s(s); return 0; }
编译器会对关于此行为的更改发出警告。
警告 C4587:U::s:行为更改:不再隐式调用构造函数 警告 C4588:U::s:行为更改:不再隐式调用析构函数
无论编译器版本为何,非匿名结构的运行时行为都是相同的。
#include <stdio.h> struct S { S() { printf("Creating S. "); } ~S() { printf("Destroying S "); } }; union U { struct { S s; } namedStruct; U() {} ~U() {} }; void f() { U u; } int main() { f(); char s[1024]; printf("Press any key. "); gets_s(s); return 0; }
或者,尝试将构造函数和析构函数代码移到新的函数中,并从联合的构造函数和析构函数添加对这些函数的调用。
#include <stdio.h> struct S { void Create() { printf("Creating S. "); } void Destroy() { printf("Destroying S "); } }; union U { struct { S s; }; U() { s.Create(); } ~U() { s.Destroy(); } }; void f() { U u; } int main() { f(); char s[1024]; printf("Press any key. "); gets_s(s); return 0; }
-
模板解析
这些无效的实例化通常不会导致编译器错误,这被称为 SFINAE(替换失败不是错误)原则。
例如,考虑以下代码:
#include <type_traits> template<typename T> struct S { S() = default; S(const S&); S(S&&); template<typename U, typename = typename std::enable_if<std::is_base_of<T, U>::value>::type> S(S<U>&&); }; struct D; void f1() { S<D> s1; S<D> s2(s1); } struct B { }; struct D : public B { }; void f2() { S<D> s1; S<D> s2(s1); }
如果使用当前编译器进行编译,将得到以下错误:
type_traits(1110):错误 C2139:“D”:未定义的类不允许作为编译器内部类型特征“__is_base_of”的参数 .. 331.cpp(14):备注:请参阅“D”的声明 .. 331.cpp(10):备注:请参阅对正在编译的类模板实例化“std::is_base_of<T,U>”的引用 替换为 [ T=D, U=D ]
这是因为在第一次调用 is_base_of 时,尚未定义类“D”。
如果定义位于标头文件中,请检查标头文件的 include 语句的顺序,以确保在使用有问题的模板之前,对任何类定义进行了编译。
-
复制构造函数
在 Dev14 中,此隐式生成的复制构造函数也标记为“= delete”。
-
重构的二进制文件
CRT 库功能。
-
localeconv
在早期版本的库中,此函数将返回全局区域设置(而不是线程的区域设置)的 lconv 数据。
如果使用每个线程区域设置,应该检查 localeconv 的使用以查看你的代码是否假定返回的 lconv 数据代表全局区域设置,并相应地对其进行修改。
-
数学库函数的 C++ 重载
现在,已从 <math.h> 中删除了所有 C++ 重载,现在仅包含在 <cmath> 中。
下表列出了移动的函数。
移动的函数:
-
双精度型 abs(double) 和浮点型 abs(float)
-
双精度型 pow(double, int)、浮点型 pow(float, float)、浮点型 pow(float, int)、长双精度型 pow(long double, long double)、长双精度型 pow(long double, int)
-
浮点型和长双精度型版本的浮点函数 acos、acosh、asin、asinh、atan、atanh、atan2、cbrt、ceil、copysign、cos、cosh、erf、erfc、exp、exp2、expm1、fabs、fdim、floor、fma、fmax、fmin、fmod、frexp、hypot、ilogb、ldexp、lgamma、llrint、llround、log、log10、log1p、log2、lrint、lround、modf、nearbyint、nextafter、nexttoward、remainder、remquo、rint、round、scalbln、scalbn、sin、sinh、sqrt、tan、tanh、tgamma、trunc
这将产生错误:
警告 C4244:“参数”:从“float”转换为“int”,可能丢失数据
此警告的解决方法是将对 abs 的调用替换为浮点版本的 abs(例如双精度型参数的 fabs 或浮点型参数的 fabsf)或包含 cmath 标头并继续使用 abs。
-
-
浮点一致性
C11 标准的附录 F。
这些更改不会导致编译时错误,但可能会根据标准使程序以不同的方式更准确地运行。
-
FLT_ROUNDS
FLT_ROUNDS 宏现在是动态的,并正确反映当前的舍入模式。
-
new 和 delete
这些运算符函数现在始终以静态方式链接到二进制文件,即使是使用运行时库 DLL 时也是如此。
请注意,/clr:pure 在Visual Studio 2015 RC 中已被弃用,并且可能在未来版本中删除。
-
_beginthread 和 _beginthreadex
这有助于确保线程在完成运行之后才卸载模块。
-
va_start 和引用类型
C++ 标准禁止引用类型的参数。
-
Printf 和 scanf 系列函数现在采用内联方式进行定义。
如果可能,应更新代码以包括 CRT 标头(即,添加 #include <stdio.h>)和内联函数,但如果不想修改代码以包括这些标头文件,则可以选择将其他库添加到链接器输入 (legacy_stdio_definitions.lib)。
若要将此库添加到 IDE 中的链接器输入,请打开项目节点的上下文菜单,选择“属性”,然后在“项目属性”对话框中选择“链接器”,编辑“链接器输入”以将 legacy_stdio_definitions.lib 添加到用分号隔开的列表。
如果库是第三方库并且第三方库的源不可用,则应请求来自第三方更新后的二进制文件,或者将你对此库的用法封装到单独的 DLL(使用旧版 Visual C++ 或库编译的)。
在这种情况下,应通过将 legacy_stdio_definitions.lib 添加到链接器输入(如上文所述)来解决该错误。
请尝试使用下面的命令行来查看在库中定义的符号。
dumpbin.exe /LINKERMEMBER somelibrary.lib
-
gets 和 _getws
_getws_s。
-
_cgets 和 _cgetws
_cgetws_s。
-
无穷大和非数字 (NaN) 格式设置
在早期版本中,可以使用 Visual C++ 特定的 sentinel 字符串集进行无穷大和 NaN 格式设置。
-
无穷大:1.#INF
-
静默 NaN:1.#QNAN
-
信号 NaN:1.#SNAN
-
不定 NaN:1.#IND
新字符串如下所示:
-
无穷大:inf
-
静默 NaN:nan
-
信号 NaN:nan(snan)
-
不定 NaN:nan(ind)
如果使用了大写格式说明符(%F 而不是 %f),则字符串将按要求以大写字母形式(INF 而不是 inf)打印。
scanf 函数以便分析这些新的字符串,因此这些字符串会通过 printf 和 scanf 往返。
-
-
浮点格式设置和分析
strtod 这样的函数。
作为改进的一个例子;打印两个中指数较大的一个时,请考虑结果:
旧版本:1208925819614629200000000 新版本:1208925819614629174706176
新版本的结果始终比旧版本的结果更准备。
-
十六进制和无穷大/NaN 浮点分析
浮点分析算法现在将分析十六进制浮点字符串(例如,那些由 %a 和 %A printf 格式说明符生成的字符串)和由 printf 函数生成的所有无穷大和 NaN 字符串(如上文所述)。
-
%A 和 %a 零填充
已解决此问题。
-
%A 和 %a 精度
为了符合 C 标准,现在默认精度为 13。
精度规范。
-
%F 说明符
它在功能上等效于 %f 格式说明符,但使用大写字母形式进行格式设置的无穷大和 Nan 除外。
如果遇到 %F,现在则将其视为 %F 格式说明符;如果遇到 %N,现在则将其视为无效的参数。
-
指数格式设置
这是错误的:根据 C 要求,如果可使用一个或两个数字表示指数,则仅打印两个数字。
已将默认行为更改为符合标准的指数打印模式。
-
格式字符串验证
现在所有无效格式字符串都被视为无效的参数。
-
fopen 模式字符串验证
现在可检测无效的模式字符串并将其视为无效的参数。
-
_O_U8TEXT 模式
在早期版本的库中,它将报告正在 _O_WTEXT 中打开的此类流。
如果你的应用程序不支持 UTF_8,请考虑为此越来越常见的编码添加支持。
-
snprintf 和 vsnprintf
vsnprintf 在包括 <stdio.h> 之前定义为宏,则现在编译将因出错而失败,该错误指示定义了宏的位置。
通常情况下,解决此问题的方法是删除用户代码中 snprintf 或 vsnprintf 的任何声明。
-
tmpnam 生成可用文件名
这些函数现在在临时目录中生成可用的文件名路径。
-
文件封装
作为此操作的一部分,<stdio.h> 中所定义的文件现在是不透明类型且无法从 CRT 自身外部访问其成员。
-
_outp 和 _inp
_inpd。
-
strtof 和 wcstof
(请注意此错误只特定于这两个函数;strtod、wcstod、strtold 和 wcstold 函数不受影响。) 这是运行时重大更改。
-
对齐的分配函数
这是运行时重大更改。
-
堆函数
这些函数已不起作用,因为 CRT 已更新为使用 Windows 堆。
-
smallheap
链接选项。
-
wcstok
在早期版本的库中,此函数的签名为:
wchar_t* wcstok(wchar_t*, wchar_t const*)
该函数现在具有签名 wchar_t* wcstok(wchar_t*、wchar_t const*、wchar_t**),并要求调用方将上下文作为第三个参数传递给函数。
在 C 代码中,你可能会定义 _CRT_NON_CONFORMING_WCSTOK 以使 _wcstok 用于替换 wcstok。
-
clock
QueryPerformanceCounter 重新实现了 clock 函数,现在它是单一的。
-
fstat 和 _utime
在 Visual Studio 2013 之前的版本中,所有这些函数错误调整标准时时间,就像处于夏时制时间内一样。
现在已修复 fstat 和 _utime 系列函数,因此所有这些函数现在可正确且一致地处理夏时制。
-
asctime
已解决此问题。
-
strftime 和 wcsftime
此外,分析但忽略了 E 和 O 修饰符。
已解决此问题。
-
timespec 和 TIME_UTC
这对于在任一这些方面具有冲突定义的代码而言,是一项重大更改。
-
CLOCKS_PER_SEC
现在,CLOCKS_PER_SEC 宏根据 C 语言要求扩展为整数类型 clock_t。
(_MSC_VER 是包含编译器主版本的宏,例如,Visual Studio 2013 的 1800。) 此检查无法检测 DLL 混合,也无法检测涉及 Visual C++ 2008 或早期版本的混合。
-
STL 包含文件
第二,<tuple> 现在用于声明 std::array 但不包括所有 <array>,这可能中断代码通过以下代码构造的组合:代码具有名为“数组”的变量,你具有 using 指令“using namespace std;” 和你包括含有 <tuple> 的 STL 标头(如 <functional>),其现在用于声明 std::array。
-
steady_clock
因此,在 Visual C++ 中,steady_clock::time_point 现在是 chrono::time_point<steady_clock> 的 typedef;但是,其他实现不一定是这种情况。
-
分配器和 const
现在,我们要求分配器进行相等/不等比较,以接受两端上的 const 参数。 如果你的分配器如下定义这些运算符:
bool operator==(const MyAlloc& other)
你应更新这些以将它们声明为 const 成员。
bool operator==(const MyAlloc& other) const
-
const 元素
在当前版本中,此类容器无法编译。
-
std::allocator::deallocate
在运行时,为 n 传递不同于标准要求的参数的代码可能会崩溃。
-
hash_map 和 hash_set
请改用 unordered_map 和 unordered_set。
-
比较运算符和 operator()
现在比较运算符类声明中的以下代码无法进行编译:
bool operator()(const X& a, const X& b)
若要解决此错误,请将函数声明更改为:
bool operator()(const X& a, const X& b) const
-
类型特征
下表显示了旧名称和新名称。
旧名称
新名称
add_reference
add_lvalue_reference
has_default_constructor
is_default_constructible
has_copy_constructor
is_copy_constructible
has_move_constructor
is_move_constructible
has_nothrow_constructor
is_nothrow_default_constructible
has_nothrow_default_constructor
is_nothrow_default_constructible
has_nothrow_copy
is_nothrow_copy_constructible
has_nothrow_copy_constructor
is_nothrow_copy_constructible
has_nothrow_move_constructor
is_nothrow_move_constructible
has_nothrow_assign
is_nothrow_copy_assignable
has_nothrow_copy_assign
is_nothrow_copy_assignable
has_nothrow_move_assign
is_nothrow_move_assignable
has_trivial_constructor
is_trivially_default_constructible
has_trivial_default_constructor
is_trivially_default_constructible
has_trivial_copy
is_trivially_copy_constructible
has_trivial_move_constructor
is_trivially_move_constructible
has_trivial_assign
is_trivially_copy_assignable
has_trivial_move_assign
is_trivially_move_assignable
has_trivial_destructor
is_trivially_destructible
-
launch::any 和 launch::sync 策略
launch 枚举。
-
可从控制面板、程序和功能,或从安装媒体重新运行 Visual Studio 安装程序。
Visual C++ 可再发行组件包仍包含此库。
-
与 concurrency::Context::Yield 冲突的 Windows.h 中的 Yield 宏
若要解决与 Yield 的冲突,可以改为更新代码以调用 YieldExecution 函数,或在调用站点用括号将 Yield 函数名括起来,如下例所示:
(concurrency::Context::Yield)();
http://www.cnblogs.com/oloroso/articles/5310565.html