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