VS2015 Update 2中关于STL的一些修复


[原文发表地址] VS2015 Update 2中关于STL的一些修复
[原文发表时间] 2016/4/14

除了10个新特性(和16个类库问题尚待确认)以外,STL在VS 2015 Update 2中还包含了许多针对准确性和性能上的改进。(之前的bug修复日志:RTM Part1,RTM Part2,和Update 1。此外,我们也有一个关于Update 2中编译器和IDE的修复列表。)实施这些STL修复的人员包括Billy O’Neal (@MalwareMinigun),Steve Wishnousky,James McNellis (@JamesMcNellis),和我自己(@StephanTLavavej)。
 

 

关于Iostreams Floating-Point的性能/准确性的修复

 

在Update 2中,根据我的机器测量,“stream >> dbl”快了19.5倍,并且“stream >> flt”快了17.0倍。是的,这是时间,不是百分比。这完全颠覆了(而且还远远不止如此!)在VS2010和VS2012之间被引进和在VS2013与VS2015之间被恶化的性能回归。此外,每一个写入位都是正确的,这是经过反复循环所有限定的32-bit floats和给复杂的64-bit doubles加上一个回归测试来验证的。这样修复了许多bug(VSO#103391/Connect#1540816, VSO#115506/Connect#1601079, VSO#120754/Connect#1311640, VSO#181316/Connect#2245376,和更多的重复问题)。我知道怎么在Update 2中修复这个问题并且保证二进制的完整性。在下一个类库的主要版本中将会对这些问题修复地更加全面,在那里,我会删除31个资源文件并且缩减为12KB STL的DLL文件。这里是我做的性能测试。我测试了100万个32-bit floats和100万个64-bit doubles,避免了了内存分配问题并且平均都跑了10次。我比较了CRT中的strtof()和strtod()的iostreams:

Version

CRT float

STL float

CRT double

STL double

VS 2010

362.8 ms

930.1 ms

362.4 ms

929.7 ms

VS 2012

329.0 ms

6699.2 ms

329.0 ms

7939.3 ms

VS 2013

324.2 ms

6510.5 ms

337.2 ms

7972.7 ms

VS 2015.1

214.7 ms

12728.2 ms

247.4 ms

15244.5 ms

VS 2015.2

215.7 ms

747.7 ms

251.2 ms

780.2 ms

是得,尽管iostreams比以前快,但是它仍然比CRT慢。虽然未来我们能够或多或少地改善iostreams,但这都是预料之中的。

至于准确性,这里是反复循环所有限定的32-bit floats的结果。这是float ==> string ==> float,不包括INF/NAN,测试了10进制和16进制:

Version

Failures

VS 2013

332,185,880

VS 2015.1

245,302

VS 2015.2

0

主要性能的修复

多年来,在可能的时候,STL 有一个关于调用memmove()的优化,这种优化可以使它的速度比核心语言更快。。它通过元编程启动,并且应用于矢量分配,copy(),以及其他地方。在Update 2中,当这种优化被启动时, 它表现出明显的优势,,代码速度将会快到9.5倍(VSO#179897, VSO#180469)。本质上的优化要求迭代器在”unwrapping”之后成为指针(所以 容器/数组/字符串 迭代器都可以)。为激活这个优化,我们需要指向的元素类型发生变化。以前,我们需要它们变成相同的标量数据类型(忽略常量类型,所以将const int拷贝到int是可以的)。标准中定义标量类型是(可能cv-qualified)算法,枚举,指针,指向成员的指针和nullptr_t。(事实上我们在这里存在一个bug,当我们尝试为将一个int变量拷贝到int变量激活优化时,在调用memmove()时编译失败。但是10多年来从没人抱怨过这个!可见这个bug 隐藏地极深)。我们有一个特别的技巧:拷贝X * 元素到const X * 元素是可以通过memmove()函数执行。-尽管它们是不同的标量类型,我们知道它们的表示法是相同的。

重要的是,用户自定义类型不是标量,所以它们永远无法进行优化,即使它们是像包含两个int类型的结构一样简单。在Update 2中,Billy重修了这个逻辑,扩展到了一般的可复制类型,哪怕它们是用户自定义的。我们花费了很多时间来思考一个新的元编程, 这种元编程在保留完整的精准性上面表现出明显的优势。新的标准是:像往常一样,迭代器必须在unwrapping之后变成指针。对于元素,我们忽略常量性但是拒绝可变性。(注意,关于如何忽略常量性我们很谨慎 – 从int类型拷贝到 const int类型无法编译)。当元素的类型都是相同的时,我们依赖is_trivially_copyable的type trait ,这可以识别UDT对memmove()是安全的性。当元素的类型是不同的时,我们已经保留了我们的“X* 到 const X*”这一特例,并且我们还在增加更多。现在我们要拓展搭到枚举类型下面的的基本整数类型,并且我们会问,”是不是这些都是整数类型,它们的大小相同吗?它们是不是都是布尔类型或者都是非布尔类型?“。这意味着我们将可以激活memmove()来优化int拷贝到unsinged int,但是不能从short拷贝到int。(我们也要求is_trivially_assignable来做进一步的确认,从而确保我们不会忽略任何一个用户自定义的赋值操作符。注意,无论如何is_trivially_assignable自己也无法做到全部的检测。将short赋值给int是一个”毫无价值“的操作,但是memmove()不能完成它,因为元素的大小不同)。最后,当激活memmove()进行优化时,代码生成的大小会略小一些。

TLDR:vector和copy()的速度更快了,耶。你需要做的就是安装Update 2并且重新编译。

* <string>:to_string(integer) 提升了11倍速度(VSO#153106)。

* <string>:string::replace() 可以更快地替换更大的同大小的子字符串(VSO#174871/Connect#2155092)。

* <string>:string::push_back() 提升了3倍速(VSO#116522/Connect#1263155)。

* <regex>:由于避免临时构建std::strings,sub_match 的比较速度更快了(VSO#177524)。

次要性能的修复

* <functional>:我略微提高了拷贝std::functions的执行时间和代码生成的大小。

* <algorithm>,<memory>:fill()/fill_n()/uninitialized_fill()/uninitialized_fill_n() 这些函数在调用memset()时具有明显的优势。

* <vector>:vector’s ctor(N) and resize(N) 在为all-bits-zero类型调用memset()具有明显的优势。

* <string>:basic_string::compare(const basic_string&) 由于有一个后端的bug或者是限制影响了它的noexcept标记 (VSO#187864),所以只有次一级的代码生成优化。在Backend在未来改善之前,类库已经回避了这一问题。

* <complex>:我通过在 infinity()/quiet_NaN()使用<limits>来略微改善了代码生成, (自Update 1开始) 这是通过头文件编译器钩子函数来驱动。

文件系统的修复

* <experimental/filesystem>:canonical() 不能处理类似“../../meow”这样的地址 (VSO#121275/Connect#1629169,VSO#144257/Connect#1760162)。

* <experimental/filesystem>:recursive_directory_iterator::disable_recursion_pending() 一直受到迭代器的影响,但是TS说它应该只是影响下一个增量(VSO#171729)。

* <experimental/filesystem>:equivalent(),hard_link_count(),和 last_write_time() 当参数为目录执行失败(VSO#121387/Connect#1631910)。

* <experimental/filesystem>:copy() 在很多方面并不遵守TS的规范说明(VSO#153113)。

正则表达式的修复

* <regex>:当执行匹配时,嵌套量词的某些模式会触发死循环 (VSO#91764, VSO#167760) 。

* <regex>:当一种模式是一个字符类突然用反斜杠结尾,在构建这种无效模式的正则表达式时会发生崩溃 (VSO#159685/Connect#1961300)。 现在,regex(R”([\)”) 按照标准文档的规定抛出error_escape。

* <regex>:尽管在之前的主要版本中有一些修复,但是正则表达式在某些包含了高位非ASCII字符的模式中还是会崩溃 (VSO#153556/Connect#1852540,VSO#178495/Connect#2218322)。 我们相信这一问题现在已经被修复了,一劳永逸。

* <regex>:我们拒绝通过抛出regex_error来尝试退出非特殊字符,如 regex(R”(\z)”) (VSO#101318)。令人惊讶的是,标准文档也不清楚这样做是否是有效的。C ++标准文档参考了那个充满了让人难以理解的废话的ECMAScript 3标准。我希望我是在开玩笑,但是这件事非常重要–IdentityEscape的规范是完全无法执行的。 ECMAScript 6同样受到了影响,除了它的那个规范性附录B“Additional ECMAScript Features for Web Browsers”,它明确规定了什么是合理的 (即每一个非特殊字符都可以被转义,除了字母‘c’)。经过测量各种STL的表现和浏览器的运行,包括Boost,libstdc++,Microsoft Edge和Google Chrome,它们都认可\z作为一个IdentityEscape,只有 libc++是唯一拒绝它的库,Billy遵循ES6 附录B改变了我们的实施,并且承认了它 (这看起来是一个直观的、合理的行为)。 他也在LWG 2584提交一个明确作为C++ Standard的补丁的建议性解决方案。

功能性的修复

* <functional>:通过Microsoft Connect和IDE的 Send-A-Smile,用户报告了关于std::function隐式转换的bug (VSO#95177,VSO#108113/Connect#1188553)。这些bug在我实施C++14 std::function SFINAE时被修复了。(是的,我要厚脸皮的分开计算功能和bug修复。)

* <type_traits>:用户同样提交了一个bug来报告result_of::type没有完全清除(VSO#105317/Connect#1548688,VSO#120635)。这个问题通过C++14 result_of SFINAE修复了。

* <functional>:在执行bind()时,将C4100级别提高到3级 (或更加紧要) 触发了一个“unreferenced formal parameter”的警告,否则将正常执行(VSO#177244/Connect#2209399)。这实际上不是一个被支持的方案–在警告的水平没有改变的前提下,STL尝试清理/W4 /analyze 选项 (并且按此进行广泛的测试),。然而,这是一个明显的疏忽,所以我通过删除名称来修复它。 (事实上我在整个STL中修复这个bug, 这种修复方法受到支持可变参数模板的编译器的限制) 。

* <type_traits>:is_function 不支持“weird”类型的函数(VSO#152213/Connect#1882163)。这些奇怪的事情是由于分解cv/ref-合格的PMF类型而产生的(如果你不知道这些是什么,你也不必去知道)。它们之所以奇怪是因为这是不可能出现引用它们(像 cv void)或者指向它们(不像 cv void)。 我在STL上处理了这些问题,实施 C++14 LWG 2196 “Specification of is_*[copy/move]_[constructible/assignable] unclear for non-referencable types” 和 C++17 LWG 2101 “一些转换类型可以产生不可能存在的类型”。

随机功能的修复

* <random>:shuffle_order_engine::seed() (包括knuth_b::seed()) 运行不正常 (VSO#95604)。 现在它们正常运行。
* <random>:xor_combine 已经删除了。 这是一个标准文档中故意省略的一个TR1-only 类。

多线程的修复

* <thread>,<condition_variable>等: this_thread::sleep_for(),condition_variable::wait_for()等函数的持续时间参数为类似1.5s的float型数据时,会引起编译错误。(VSO#122248/Connect#1636961,VSO#106920/Connect#1179590,VSO#153606/Connect#1839243)

* <thread>:this_thread::sleep_until() 在使用自定义的,不是从1970年开始的时钟时运行不正常(VSO#99350,VSO#123833,VSO#144114/Connect#1743217)。 这包括steady_clock/high_resolution_clock 这两个有着可变时间的时钟。

* <future>:在我实施完成SFINAE表达式的result_of功能后,Billy根据标准文档 重新修改了async()函数 (VSO#97632)。这修复了在调用async()函数时出现的某些编译错误。

* <future>:async() 和bind()不能正确的一起执行,对于movable-only类型的参数时会出现编译错误 (VSO#112570/Connect#1582910,VSO#111640)。 async() 通常不能激活bind()的类似占位符一样的类型的特殊处理 (VSO#115515)。

* <future>:promise<T> 运行正常,但是在设置值之前破坏promise<T&>/promise<void> 将不会存储一个broken_promise的异常 (VSO#152487/Connect#1877917)。 我通过仔细的复制粘贴代码修复了这个破损的broken_promise约定。

* <atomic>:我修复了一个正在影响 atomic<UDT> 的看起来小但是很烦人的编译bug(VSO#152725/Connect#1892487)。 如果你碰到了这个atomic<UDT> bug,你将在升级的时候迎接一个static_assert,这里我解释一下发生了什么:“使用等于2/4/8的sizeof(T) 并且alignof(T)< sizeof(T)去实例化std::atomic<T>。 在 VS 2015 Update 2之前,这样做会在执行时发生错乱。 VS 2015 Update 2 中它被修复了,可以正确的执行,但是这种修改从根本上改变了布局并且打破了二进制的兼容性。 请定义_ENABLE_ATOMIC_ALIGNMENT_FIX 来说明你对这些了解了,还有你链接的这些都是在VS 2015 Update 2 (或者更新版本)编译器上进行的。”

这里有一个在x86上和atomic<SizeEightAlignEight>相关的bug,这种情况非常地奇怪因为它的堆栈排列方式只是4对齐。SizeEightAlignEight 类型的例子有:long long,double,StructWrappingLongLong,和StructWrappingDouble。 在布局中,这些类型通常在编译的时候是按照8对齐,所以 struct Outer { char c; SizeEightAlignEight meow; }; 包含了7个字节的填充。 然而,编译器在运行代码的时候将不会自动地排列堆栈,所以SizeEightAlignEight 类型的变量可以出现在堆栈的4字节的边界上。 (这是故意的 – 动态对齐并不是零成本运行,并且x86也不需要long long和double类型的数据为了正确性而按8字节对齐。 有时候8字节对齐的方式可以提高性能,所以编译器会自己去判断是否使用动态对齐。)

在Update 2之前,以8字节对齐的布局也是适用于atomic<SizeEightAlignEight> 的,但是不会触发堆栈内动态对齐。 这样是不好的,因为它需要以8字节为边界来保证准确性。 在Update 2中,对此做了部分修复。 现在atomic<double>,atomic<StructWrappingLongLong>,和 atomic<StructWrappingDouble> 都有标记为alignas(8)的数据成员。这意味这些数据的布局是不能改变的(所以它们不会触发之前提到的static_assert消息),但是现在它们会为了准确性而触发动态对齐。

不幸的是,直到刚才我都没有意识到atomic<long long> (和 atomic<unsigned long long>) 受到了影响,并且没有在Update 2中进行修复。这是因为存在atomic<integral>特化来提供整形运算符,并且我的alignas修复方式影响了atomic<UDT> (经常食用atomic<double> ,尽管double类型不是UDT)。我提交了一个bug并分配给了我自己(VSO#212461)。 如果你在x86上使用atomic<long long/unsigned long long>,你可以通过自己设置alignas 来解决这个问题。

其他的修复

* 现在STL 可以不抛出“警告C4265:类有虚函数,但析构函数不是虚拟的” (VSO#146506/Connect#1802994)。这个警告是默认关闭的,但是我们已经设置了一些异常信息, 用来明确STL 等规定, 这也会涉及到在默认情况下关于/W4 /analyze 的相关警告。

* <cmath>:std::pow() 在运行某些边界参数时会出错,类似 pow(2.0,-1024) (VSO#99869)。

* 在RTM和Update 1之间,编译器就停止了导出ctype<char>::table_size,因为这种导出从理论上来讲会影响到我们一直以来极力保护的msvcp140.dll的二进制兼容性。由于使用table_size看似并没有引起什么麻烦,但是使用它仍然不是一件好事 (VSO#163808)。为了Update 2修复了编译器,恢复了RTM的二进制兼容性,并且Billy还加了一个测试(后来迁移到了类库build上)来验证RTM的DLL文件出口是未改变的。这个测试至少能避免测试用例意外损坏(当我错误的想要修改输出文件的时候),这证明了它的价值。

* <vector>,<string>,<deque>:我们将C++14 N3644中的 Null Forward Iterators功能放在了2015 RTM上。我们实施了标准文档上的要求,但是后来发现标准文档上的说明是不完全的。这个功能明确规定当forward iterators (由客户或者标准文档提供的) 是有初始化值时,它们应该是equality/inequality comparable 并且表现的像一个空的range。(注意这些迭代器仍然不能被比作指向容器的迭代器,当它们指向不同容器内时也不能进行比较.) 这是个不易察觉的问题:随机访问迭代器还有其他操作(像是减法运算),并且对于进行了初始化值的迭代器来说,它们不必是有效的。迭代器强度通常由算法决定,准确的说是因为它们想做有效率的事情,比如减去ranits。如果值初始化的迭代器表现的不像一个空的range,所有符合迭代器强度的操作将不能使用。这我们早已将这种方式实施了在release模式下,但是debug模式下的检查过于严格了。Billy 审核了STL,修复了受影响的随机访问迭代器(在 vector,string,和deque上; array上早就允许 ),并且添加了测试方案来保它们继续工作 (VSO#199326)。(我仍然需要提交一个关于这个类库bug。标准文档中是否早已表明ranit操作的这些要求还是存在争议的,但是因为我们原来并没有对这种需求作出解释,所以我觉得最起码这里是需要添加一个备注的。)

* <algorithm>,<memory>:自VS 2005起,当算法如copy(first,last,result)一样将[first , last)拷贝进[result , …),在半程参数中调用原始指针时,STL就会发出警告 (或是任意与STL提供的迭代器不同的迭代器)。这是因为在debug模式下半程范围的大小无法检查。当数组被当作参数直接传递时,我们使用重载来避免这些警告 (编译时可以检测数组大小)。如果你曾经看过或者调试过STL的代码,你也许会见过广泛的元编程被要求发出这些警告。在Update 2中,Billy 简化了 copy()/copy_n()/copy_backward()/move()/move_backward()/uninitialized_copy() 这些函数如何发出警告,提高了编译时的吞吐量和警告消息的质量。(Update 3中会有更多的改善)。 此外,fill_n() 不再发出这些警告了,同时fill_n(first,n,value) 有一个独立的迭代器,这个迭代器指向的全部区域是[first,first + n)。

* 容器的移动构造应该支持去移动构造它的分配器,但我们还是进行拷贝构造。(N4582 23.2.1 [container.requirements.general]/8,VSO#102478)。他们之间的区别跟std::allocator或是大多数用户自定义的分配没关系,但是我们要按照标准文档说的去做。Steve修复了所有的STL,所以现在当需要的时候,我们可以移动构造分配器。

* <typeinfo>:当_HAS_EXCEPTIONS 设置为0的时候这个头文件不编译。(VSO#115482/Connect#1600701)。这种模式在当前是未经证实,未经测试和不被支持的(也许在未来会有改变,但是不是在Update 2)。无论如何,这个头文件现在是禁用异常的。

* <algorithm>:min() 和 max() 现在都被标记了noexcept,增强了它们在标准文档中的描述(VSO#118461)。这只是内部工具的一个例外,不能代表我们总方针的改变(到目前为止,我们大多时候都避免增强noexcept).

* STL中的大部分内容被强制参与进仅针对迭代器的重载解析。我们在这里用的内部机制并没有检查迭代器是不是iterator_category。类型定义通过iterator_traits提供了non-intrusively(VSO#121440)。现在它开始检查了。注意,这是从iterator_traits SFINAE中分离出来的,我们早已经实施了的。

* <scoped_allocator>:几个编译时的错误(丢失moves等等) 已经被修复了(VSO#101409,VSO#184838)。

* <exception>:这个头文件里(很讽刺?)丢失了许多标准文档中要求填加的noexcept 标记(VSO#121619)。James把它修复了,所以<exception> 保证不会抛异常了,除非它应该抛异常.

* 编译器有许多编译选项。有些是非常好的 (/Zc:strictStrings),有些是值得怀疑的 (/Gz),还有一些是很差劲的(/Zc:wchar_t-)。STL几乎容忍了所有这些选项,只有少数几个除外(/Zc:auto- 这个显然无法忍受)。/RTCc 选项一直很有问题(VSO#91800)。无论是编译器还是IDE都是默认启用它,但是如果启用它(只在debug下构建),它检测运行时值的截断并且终止运行程序。如果这种事情只发生在未定义的行为上,这将是非常棒的。不幸的是,根据标准文档,/RTCc选项只在明确定义的代码中检测截断(例如将 unsigned long long 截断到 unsigned short 是一个确定的过程,这是有保证的操作)。当static_cast执行截断操作时它会被触发。(只有位屏蔽功能可以迁就/RTCc选项。) 审计STL中的那些鲜为人知的、非标准的选项是一个没有多少价值但又很繁琐的工作。因此,在Update 2中,我阻止/RTCc和STL头文件一起使用,通过一个静态断言:”/RTCc不遵循代码,所以在C++标准类库中不支持它。无论是移除这个编译选项还是通过定义_ALLOW_RTCc_IN_STL来承认你收到的这个警告。” 如果你启动escape hatch,你将会得到我们以前的那些通用运行方式.

* <utility>:用户抱怨我们在类库中实施的make_integer_sequence比较差劲,它在执行大型数列时会出现许多问题 (VSO#97862/Connect#1499922)。他们要求实施一些智能的对数,就像在其他类库上看到的一样。我告诉我自己,“这个听起来很难。我宁愿让编译器来做我的工作。” 所以我申请并收到了一个来自C1XX和Clang的和编译器挂钩的工作,这个工作有能力处理make_integer_sequence (并且根据设计这个hook可以驱动任何具有相似性能的东西;我考虑过Boost)。注意,自Update 2起,我仍然需要为EDG实施线性的类库,所以智能感应可能不喜欢巨大的数列。

* <tuple>:我通常不喜欢去计算开发过程中遇到的和消失的bug,但是我想说一个在Update 2 RC 和 Update 2 RTM之间的紧急修复的事情。当我在N4387中为pair/tuple C++17的新规则时,我准确的限制了元组的构造函数,但是却没有注意到它们是怎么和递归互动的。这导致了在某些类型下重载解析失败 (VSO#191303/Connect#2351203)。我重修了元组来避免这个问题(其余特性不变)。现在,元组执行C++17 规则(如LWG 2549中的必要的修改),根据我的知识和能力去做到最好。注意,这些规则对1-tuples来说是一个重大变化,在这你可以使用tuple<UDT> ,并且UDT有一个不受限制的模板构造函数。举个例子,当你尝试从tuple<Other>去构造tuple<UDT>时,新规则需要计算出你是否要转换元组(如从Other构造UDT),或者你是否提供了一个包含UDT的参数 (如从tuple<Other>构造UDT)。如果你的UDT看似是从 tuple<Other>进行构造,那么这种方式就会被选择。 (考虑到安全原因,在LWG 2549中移除了元组转换构造。) 如果你觉得不能理解,你可以安全的忽略它。如果你是一个与元组交互的高性能代码的维护人员,并且你的构造函数没有适当的被限制,那你就需要了解一下以上内容。

* <unordered_meow>:无序容器交换元素时不能正确的交换它们的max_load_factors。事实上,它们交换了两次,这种方式是不好的。现在它们交换三次。(好吧,只有一次。)

* <memory>:我移除了一个bad_weak_ptr的非标准构造函数。

编译器修复

在这些更新日志中,我没有列举编译器的修复,尽管它们影响到了STL。(这其中包括了修复编译器hook。) 这里的仅仅只是许多需要我来保持追踪的编译器修复,许多我也不是很懂。 (我追踪所有要提交到STL的问题,我也去增加所有我们仍然需要的编译器的东西。) 无论如何,有一个编译器的问题值得在这提一下。当Tanveer Gani 在2015 RTM编译器中实施constexpr,并且我们因此需要复查,我们几乎完全避免了回归问题。尽管许多关于constexpr的编译bug已经在Update 1和Update 2中都修复了(Update 3来了更多),但那些在STL中的那些使用了最新的constexpr的以前代码(但是不要求constexpr 赋值) 几乎完全不受影响。有一个例外是<limits> 拒绝在/Za选项下编译,就是那个 “要求额外的一致性和 附加的编译bug” 的选项(VSO#122298/Connect#1331482)。Tanveer 在Update 2中把它修复了,所以现在 <limits> 可以和/Za选项一起运行。而且事实上,我恢复了关于/Za在STL中的测试 – 所以,虽然我们不建议使用它,但是我们还是支持这一功能。

重大的改变

我们非常努力的在更新中去确保二进制兼容性,所以你可以做类似兼容Update 2的对象文件和RTM的静态类库这样的事情。无论怎么做,Update 2还是可以很好的编译任何东西 (这样,所有在Update 2上的修复都会生效,并且你还会得到较小的二进制文件).

至于源代码兼容性,你可能已经注意到了,C++是一个复杂的语言。类库的变化偶尔会破坏用户的源代码。这里是我注意到的关于源的重大改变:

* /RTCc选项阻塞了,上文提到过。修复方式:不要使用/RTCc选项。

* 原子类型对齐的修复,上文提到过。修复方式:定义 _ENABLE_ATOMIC_ALIGNMENT_FIX 并且使用Update 2重新编译所有文件。

* (难以理解) tr1::xor_combine 和bad_weak_ptr(const char *) 被移除,上文提到过。修复方式:不要使用非标准的东西.

* 如上面所提到的,在UDT有不受限制的模板构造函数时,从tuple<Other>构造tuple<UDT>,这种情况在N4387和LWG 2549被改变了。修复方式:适当限制UDT的模板构造函数。

* 无独有偶,N4387 破坏了有关“变异的拷贝构造函数”的代码,见VSO#198760/Connect#2442329。修复方式:不要模仿auto_ptr。

* 数月前,我敢说std::function SFINAE 是不会变化的,因为它仅仅是当定义会有变化时引起<Signature>的构造函数的声明从存在到消失。但是现在我看到了两个不同的涉及到用户代码的源文件破坏。第一,在 C++11 和Update 1的不受限制的std::function 的构造函数,用户的代码可能说了些东西像decltype(user_func1(function<Signature>(bogus_callable_obj))) – 或者还有其他问题,decltype(user_func2(bogus_callable_obj)) 中的user_func2(const function<Signature>&) 隐式构造了一个std::function。这样的decltype是有效的,如果调用<Signature>的对象,它也会告诉你user_func1()或 user_func2() 会返回什么。这种运行将会在编译时失败。尽管是真的尝试从bogus_callable_obj (带有不同的标记) 去构造<Signature>。(在我见过的真实的例子中,真正的<Signature>的构造是来自totally_different_callable_obj,这才是它如何编译的.) 在C++14和Update 2中,这种decltypes将会编译失败,正如真实的构造一样。修复方式:正确书写你的decltypes。

* 无独有偶,std::function<Ret (Args)>(callable_obj) 需要问一个问题,“我可以带参数并传递它们去callable_obj吗” 然后 “我可以使用callable_obj的返回类型并隐式转换为Ret吗”。这可以显示前置声明类的构建中断,包括C1XX的“延迟模板解析”。例如,如果参数转换或者返回转换包含转换 unique_ptr<Derived>到unique_ptr<Base> (这是正常的并且是隐式的),但是派生类前置声明在在你构建std::function时,厄运! 在那时,派生类和基类之间的继承关系不知道,所以unique_ptrs还不是可转换的,所以std::function也不是可转换的。(在实例化发生在TU的最后,且派生的定义是已知时,真实的构造将会因为延迟模板解析而发生在被编译之前。) 注意:尽管以前负责隐藏这个问题的是C1XX的非标准行为,这种情景 (前置声明派生时构造std::function) 在标准文档中是被禁止的。修复方式:在你的派生被提供之前,不要尝试去转换unique_ptrs或构造std::functions。

影响STL使用的已知编译bug

现在你已经知道了所有安装Update 2的原因 (有许多的新功能和重要的修复),我应该提2个已知的bug,虽然它们不会阻碍你安装Update。编译器小组在Update 2上修复了许多bug,但是这两个是我们最晚发现的:

* 在为类型特征实施了P0006R0 变量模板之后(如is_same_v等),我通过在static_asserts(constexpr的语法环境)中使用它们来测试。除了它们受到了bug VSO#202394“SFINAE中的变量模板不生效”的影响,而且SFINAE还是你要使用的is_same_v等等方法的主要语法环境之外, 其他一切良好。不好的是! 这个问题早就已经在Update 3 的C1XX中修复了,但是我注意并报告这个问题太晚(3月15日)以至于不能修复在Update 2里面。庆幸的是,Clang没有受到影响。

* 我收到了一个报告,在STL使用新的编译hook来增强make_integer_sequence (如之前所说)时,如果一个无关的结构体包含了一个涉及index_sequence_for的类型定义时,会触发编译错误(VSO#207949)。这似乎不好理解,由于STL本身就使用make_integer_sequence相当多(如 in tuple_cat()) 并且它的所有测试都是通过的。如果这个问题影响到了你,你似乎可以直接使用make_integer_sequence来代替index_sequence_for来解决这个问题。(STL就是这么做的,因为一个完全不相关的编译bug,它不受影响。) 我简化了它,并且编译组将会调查修复C1XX。Clang不受影响。

下载 Update 2

Update 2的最终版在3月30号发布了。这里是一个关于所有Visual Studio的新主题的通告,这里是一个直接的下载链接

Stephan T。Lavavej (@StephanTLavavej)

高级开发工程师 – Visual C++ Libraries

stl@microsoft.com


Comments (0)