VS 2017 RTM即将发布。 VS 2017钢筋混凝土 现在提供并包含此处描述的所有更改—请试用并通过IDE的“帮助>发送反馈>报告问题”(或提供建议)发送反馈。
这是第三篇也是最后一篇关于2015年第3版和2017年RTM之间STL变化的文章。在 第一个岗位 (对于VS 2017预览版4),我们解释了2015年和2017年的情况 二进制兼容 . 在 第二岗位 (对于VS2017预览版5),我们列出了编译器和STL中添加了哪些功能(从那时起,我们已经实现了P0504R0在u placeu t/在u placeu typeu t>/在u placeu indexu t中重新访问,以及P0510R0拒绝无、数组、引用和不完整类型的变体。)
矢量大修:
我们彻底检查了vector的成员函数,修复了许多运行时正确性和性能缺陷。
*修复了别名错误。例如,标准允许v.emplaceu back(v[0]),这是我们在运行时处理错误的,以及v.pushu back(v[0]),这是我们用缺陷代码来防范的(询问“这个对象是否存在于我们的内存块中?”通常不起作用)。解决方法是按照谨慎的顺序执行我们的行动,这样我们就不会使我们得到的任何东西无效。有时,为了防止混淆,我们必须在堆栈上构造一个元素,只有在没有其他选择的情况下(例如emplace(),具有足够的容量,而不是在末尾)才会这样做(这里有一个活跃的bug,幸运的是它非常模糊——我们还没有尝试严格地使用分配器的construct()来处理堆栈上的此类对象。)请注意,我们的实现遵循标准,该标准不允许在每个成员函数中使用别名——例如,在插入多个元素时不允许使用别名,因此我们不尝试处理该问题。
*修复了异常处理保证。以前,我们在重新分配期间无条件地移动元素,从VS2010中最初的移动语义实现开始。现在,我们遵循标准的强制move-if-noexcept()模式。例如,当pushu back()和emplaceu back()被调用时,它们需要重新分配,它们会问元素:“您没有可构造的移动吗?如果是这样,我可以移动你(它不会失败,希望它会很快)。否则,您是否可以复制?如果是这样的话,我将退回到复制您(可能会很慢,但不会损害强大的异常保证)。否则,你是说你只能用一个可能抛出的move构造函数移动,所以我会移动你,但是如果你抛出,你不会得到强EH保证。”现在,除了几个模糊的例外,vector的所有成员函数都实现了标准要求的基本或强EH保证(第一个例外涉及到有问题的Standardese,这意味着使用仅输入迭代器的范围插入必须在范围的元素构造抛出时提供强有力的保证。如果没有英勇的措施,这基本上是不可实现的,而且没有已知的实现尝试过这样做。我们的实现提供了基本的保证:我们反复放置u back()元素,然后将它们旋转()到位。如果一个emplace_back()抛出,我们可能早就丢弃了原来的内存块,这是一个可以观察到的变化。第二个例外涉及为POCCA/POCMA分配器“重新加载”代理对象(以及其他容器中的sentinel节点),在这种情况下,我们不会对内存不足进行加固。幸运的是,std::分配器不会触发重新加载。)
*消除了不必要的EH逻辑。例如,vector的copy赋值操作符有一个不必要的try-catch块。它只需要提供基本的保障,我们可以通过适当的行动顺序来实现。
*稍微提高了调试性能。尽管这不是我们的首要任务(在没有优化器的情况下,我们所做的每件事都是昂贵的),但我们尽量避免严重或无缘无故地损害调试性能。在这种情况下,我们有时在内部实现中不必要地使用迭代器,而我们本可以使用指针。
*改进的迭代器失效检查。例如,resize()没有将结束迭代器标记为无效。
*通过避免不必要的rotate()调用提高了性能。例如,emplace(where,val)调用emplace u back(),后跟rotate()。现在,vector只在一种情况下调用rotate()(如前所述,使用仅输入迭代器插入范围)。
*锁定访问控制。现在,helper成员函数是私有的(一般来说,我们依赖于为实现者保留丑陋的名称,因此公共帮助者实际上不是bug。)
*使用有状态分配器提高了性能。例如,带有非相等分配器的move构造现在尝试激活memmove()优化(之前,我们使用make_move_iterator(),它有抑制memmove()优化的副作用。)注意,VS 2017 Update 1中有进一步的改进,其中move assignment将尝试在非POCMA不相等的情况下重用缓冲区。
请注意,此大修本质上涉及 源中断更改 . 最常见的是,标准的强制move_if_noexcept()模式将在某些场景中实例化复制构造函数。如果它们不能被实例化,你的程序将无法编译。此外,我们还利用了标准要求的其他操作。例如,N4618 23.2.3[sequence.reqmts]说a.assign(i,j)“需要:T应该从*i构造到X,从*i赋值。”我们现在利用“从*i赋值”来提高性能。
警告检修:
编译器有一个详细的警告系统,包括警告级别和push/disable/pop pragmas。编译器警告同时适用于用户代码和STL头。其他STL实现禁用“系统头”中的所有编译器警告,但我们遵循不同的原理。编译器警告的存在是为了抱怨某些有问题的操作,比如值修改符号转换或返回对临时变量的引用。这些操作同样涉及到是直接由用户代码执行,还是由代表用户执行操作的STL函数模板执行。显然,STL不应该为它自己的代码发出警告,但是我们认为在STL头中抑制所有警告是不可取的。
多年来,STL一直试图保持/W4/analyze干净(而不是/Wall,这是不同的),并通过大量的测试套件进行验证。历史上,我们在STL头中将警告级别推到3级,并进一步抑制了某些警告。虽然这使我们能够干净地编译,但它过于激进,压制了我们想要的警告。
现在,我们对STL进行了全面的检查,以采用一种新的方法。首先,我们检测您是使用/W3(或更弱的,但您永远不应该这样做)还是使用/W4(或/Wall,但这在技术上不受STL支持,您只能自己编译)。当我们感知到/W3(或更弱)时,STL将其警告级别推到3(即,与以前的行为没有变化)。当我们感知/W4(或更强)时,STL现在将其警告级别推到4,这意味着4级警告现在将应用于我们的代码。此外,我们已经审核了所有单独的警告抑制(在产品代码和测试代码中),删除了不必要的抑制,并使其余的更具针对性(有时甚至是单个函数或类)。我们还在整个STL中抑制警告C4702(无法访问的代码);虽然这个警告对用户来说是有价值的,但是它依赖于优化级别,我们相信允许它在STL头中触发会带来更多的噪音而不是价值。我们使用两个内部测试套件,加上libc++的开源测试套件,来验证我们没有对自己的代码发出警告。
这对你意味着什么。如果您使用/W3(我们不鼓励这样做)进行编译,则不应看到任何重大更改。因为我们已经修改和加强了我们的抑制,你可能会看到一些新的警告,但这应该是相当罕见的(当它们发生时,他们应该警告你让STL做的可怕的事情。如果它们很嘈杂并且不受欢迎,请报告一个bug。)如果您是使用/W4编译的(我们鼓励这样做!),您可能会看到从STL头发出的警告,这是一个 断源变化 有/WX,但是很好。毕竟,你要求4级警告,STL现在尊重这一点。例如,根据输入类型,STL算法现在将发出各种截断和符号转换警告。此外,由输入类型激活的非标准扩展现在将触发STL头中的警告。当发生这种情况时,应该修复代码以避免出现警告(例如,通过更改传递给STL的类型、更正函数对象的签名等)。不过,有逃生舱口。
首先,宏STL警告级别控制STL将其警告级别推到3还是4。它是通过检查/W3或/W4自动确定的,如前所述,但是您可以通过定义宏项目范围来覆盖它(只允许值3和4;所以,如果您想用/W4编译,但是像以前一样将STL推送到级别3,那么您可以请求它。
第二,可以在整个项目范围内定义宏u STL u EXTRA u DISABLED u WARNINGS(它将始终默认为空),以在整个STL头中抑制所选的警告。例如,将其定义为41276326将抑制“条件表达式是常量”和“常量与另一个常量的潜在比较”(我们应该对那些已经清除,这只是一个示例)。
正确性修复和其他改进:
*STL算法现在偶尔会将其迭代器声明为const。 断源更改: 根据标准的要求,迭代器可能需要将其运算符*标记为const。
*基本的字符串迭代器调试检查产生改进的诊断。
*basic_string的迭代器范围接受函数对(char*,char*)有额外的重载。这些额外的重载已被删除,因为它们阻止了string.assign(“abc”,0)的编译(这不是一个突破源头的变化;调用旧重载的代码现在将改为调用(Iterator,Iterator)重载。)
*append、assign、insert和replace的基本u字符串范围重载不再要求基本u字符串的分配器是默认可构造的。
*basic_string::c_str()、basic_string::data()、filesystem::path::c_str()和locale::c_str()现在被注释,以指示它们以null结尾。
*array::operator[]()现在被注释以改进代码分析警告(注意:我们不打算对整个STL进行注释。我们会根据具体情况考虑这些注释。)
*条件变量任意::等待直到现在接受精度较低的时间点类型。
*STDEXT::MaqyCyjkdjayalayyTyrter的调试检查现在允许C++ 14的空向前迭代器要求允许的迭代器比较。
*改进<随机>静态-断言消息,引用C++工作文件的要求。
*我们进一步改进了STL对重载操作符()和操作符()的防御。
*replace u copy()和replace u copy u if()错误地使用条件运算符实现,错误地要求将输入元素类型和新值类型转换为某些公共类型。现在它们正确地实现了if-else分支,避免了这种可兑换性要求(输入元素类型和新值类型需要分别可写入输出迭代器。)
*STL现在尊重空的花哨指针,并且不尝试取消引用它们,即使是暂时的(向量大修的一部分。)
*各种STL成员函数(如allocator::allocate()、vector::resize())都用u CRTu GUARDOVERFLOW标记。当使用/sdl编译器选项时,它将扩展到u declspec(guard(overflow)),它在函数调用之前检测整数溢出。
*在
*修复了std::variant中的一个错误,该错误导致编译器在为variant v编译std::get
运行时性能改进:
*在Traits是std::charu Traits并且分配器指针类型不是花哨的指针的常见情况下,通过使它们无分支,基本的u字符串移动构造、移动分配和交换性能提高了三倍。我们移动/交换表示,而不是单个基本字符串数据成员。
*基本的_string::find(character)族现在通过搜索字符而不是大小为1的字符串来工作。
*基本u字符串::保留不再有重复的范围检查。
*在所有分配的基本字符串函数中,删除了字符串收缩情况下的分支,因为只有reserve这样做。
*稳定的分区不再执行自移动分配。而且,它现在跳过已经在输入范围两端分区的元素。
*shuffle和randomu shuffle不再执行自移分配。
*分配临时空间的算法(稳定分区、就地合并、稳定排序)不再传递相同的基址副本和临时空间大小。
*filesystem::last u write u time(path,time)系列现在发出1个磁盘操作,而不是2个。
*对std::variant的visit()实现的性能改进很小:在分派到相应的visit函数之后,不要重新验证所有变量不是没有值的u by u exception(),因为std::visit()在分派之前已经保证了该属性。可以忽略地提高了std::visit()的性能,但大大减少了为访问生成的代码的大小。
编译器吞吐量改进:
* 断源更改:
*集中式STL算法迭代器调试检查。
比利·罗伯特·奥尼尔三世 @马尔瓦米尼贡 bion@microsoft.com
凯西·卡特 @密码键 cacarter@microsoft.com
斯蒂芬T。拉瓦维 @斯蒂芬特拉瓦维 stl@microsoft.com