Visual Studio 2017 15.3.0中的诊断改进

这篇文章以及描述的诊断学都从马克的反馈中获益匪浅, ,斯蒂芬,玛丽安,加布里埃尔, 乌尔齐伊 ,史蒂夫和 安得烈 .

null

Visual Studio 2017 15.3.0版 发布有一些改进的微软Visual C++编译器的诊断。这些改进大多是针对 诊断改进调查 我们在15.3开发周期开始时与您共享了。下面您将在成员初始化、枚举、处理预编译头、条件等方面找到一些新的或改进的诊断消息。我们将在2017年继续这项工作。

成员顺序初始化

构造函数不会按照其初始值设定项在代码中列出的顺序初始化成员,而是按照成员在类中声明的顺序初始化成员。假设实际初始化顺序与代码匹配,可能会导致多个潜在问题。一个典型的场景是,当列表中较早初始化的成员在稍后的初始化器中使用时,而由于这些成员的声明顺序,实际的初始化以相反的顺序进行。

// Compile with /w15038 to enable the warning
struct B : A
{
    B(int n) : b(n), A(b) {} // warning C5038: data member 'B::b' will be initialized after base class 'A'
    int b;
};

在当前版本中,由于在许多将警告视为错误的项目中中断了大量代码,因此上述警告在默认情况下处于关闭状态。我们计划在随后的版本中默认启用该警告,因此建议尽早启用它。

常量条件

有几点建议采用当啷声所推广的做法,即在使用附加括号时压制某些警告。我们在一个bug报告的上下文中研究了它,该报告建议当用户放入extra()时,我们应该抑制“warning C4127:conditional expression is constant”(注意,Clang本身并不适用于这种情况)。虽然我们讨论了这种可能性,但我们认为这将有损于此警告上下文中的良好编程实践,因为语言和我们的实现现在支持“if constexpr”语句。相反,我们现在建议使用“if constexpr”。

if ((sizeof(T) < sizeof(U))) …
// warning C4127 : conditional expression is constant
// note : consider using 'if constexpr' statement instead

作用域枚举

首选作用域枚举(又称枚举类)的一个原因是,它们比非作用域枚举具有更严格的类型检查规则,从而提供更好的类型安全性。  通过允许开发人员意外地混合枚举类型,我们打破了switch语句中的类型安全。这通常会导致意外的运行时行为:

enum class A { a1, a2 };
enum class B { baz, foo, a2 };
int f(A a) {
    switch (a)
    {
    case B::baz: return 1;
    case B::a2:  return 2;
    }
    return 0;
}

/放任的- 模式(同样,由于此中断的代码量),我们现在发出错误:

error C2440: 'type cast': cannot convert from 'int' to 'A'
note: Conversion to enumeration type requires an explicit cast (static_cast, C-style cast or function-style cast)
error C2046: illegal case

switch语句中的指针转换也会发出错误。

空声明

我们过去常常忽略没有任何诊断的空声明,假设它们是非常无害的。然后我们遇到了几个使用示例,其中用户在一些复杂的模板元编程代码中对模板使用空声明,并假设这些将导致空声明类型的实例化。事实并非如此,因此值得注意。在这个更新中,我们重用了在类似上下文中已经出现的警告,但是在下一个更新中,我们将把它更改为自己的警告。

struct A { … };
A; // warning C4091 : '' : ignored on left of 'A' when no variable is declared

预编译头

在大型项目中,由于在非常大的项目中使用预编译头,我们遇到了许多问题。这些问题本身并不特定于编译器,而是取决于操作系统中发生的进程。不幸的是,我们针对这个场景的“一个错误适用于所有人”不足以让用户解决问题并找到合适的解决方法。我们扩展了这些案例中包含的错误信息,以便更好地识别可能导致错误的特定场景,并就解决问题的方法向用户提供建议。

error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm13' or greater
note: PCH: Unable to get the requested block of memory
note: System returned code 1455: The paging file is too small for this operation to complete
note: please visit https://aka.ms/pch-help for more details
fatal error C1076: compiler limit: internal heap limit reached; use /Zm to specify a higher limit

更广泛的问题在我们之前的博客文章中有更详细的讨论: 预编译头(PCH)问题和建议

条件运算符

最后一组新的诊断消息都与我们对条件运算符?一致性的改进有关。这些更改也是opt-in,并由交换机保护 /Zc:三元 (由/permissive-)暗示),因为他们破坏了大量的代码。特别是,编译器用来接受条件运算符?:中被标准视为不明确的参数(参见[expr.cond]一节)。我们不再在/Zc:ternal或/permissive下接受它们,并且您可能会看到在没有这些标志的情况下编译干净的源代码中出现新的错误。

这种变化打破的典型代码模式是,某些类U同时提供了另一个类型T的构造函数和类型T的转换运算符(都是非显式的)。在这种情况下,将第二个参数转换为第三个参数的类型和将第三个参数转换为第二个参数的类型都是有效的转换,根据标准这是不明确的。

struct A
{
A(int);
operator int() const;
};

A a(42);
auto x = cond ? 7 : a; // A: old permissive behavior prefers A(7) over (int)a.
// The non-permissive behavior issues:
// error C2445: result type of conditional expression is ambiguous: types 'int' and 'A' can be converted to multiple common types
// note: could be 'int'
// note: or 'A'

要修复代码,只需将其中一个参数显式转换为另一个参数的类型。

当T表示一种以null结尾的字符串类型(例如const char*、const char16u T*等,但您也可以用数组类型和它们衰减到的指针类型来复制它)时,此公共模式有一个重要的例外,而实际参数to?:是相应类型的字符串文字。C++ 17改变了措辞,导致了C++ 14语义的变化(参见 CWG缺陷1805 ). 因此,下面示例中的代码是 接受条件/std:c++14 被拒绝/std:c++17 :

struct MyString
{
MyString(const char* s = "") noexcept; // from const char*
operator const char*() const noexcept; // to const char*
};
MyString s;
auto x = cond ? "A" : s; // MyString: permissive behavior prefers MyString("A") over (const char*)s

修复方法是显式地强制转换其中一个参数。

在触发条件运算符一致性工作的原始示例中,我们给出了一个用户没有预料到的错误,但没有说明我们给出错误的原因:

auto p1 = [](int a, int b) { return a > b; };
auto p2 = [](int a, int b) { return a > b; };
auto p3 = x ? p1 : p2; // This line used to emit an obscure error:
error C2446: ':': no conversion from 'foo::<lambda_f6cd18702c42f6cd636bfee362b37033>' to 'foo::<lambda_717fca3fc65510deea10bc47e2b06be4>'
note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

使用/Zc:triantary时,失败的原因变得很清楚,尽管有些人可能仍然不喜欢,但我们选择不优先考虑任何特定的(实现定义的)体系结构调用约定,因为我们支持多个:

error C2593: 'operator ?' is ambiguous
note: could be 'built-in C++ operator?(bool (__cdecl *)(int,int), bool (__cdecl *)(int,int))'
note: or 'built-in C++ operator?(bool (__stdcall *)(int,int), bool (__stdcall *)(int,int))'
note: or 'built-in C++ operator?(bool (__fastcall *)(int,int), bool (__fastcall *)(int,int))'
note: or 'built-in C++ operator?(bool (__vectorcall *)(int,int), bool (__vectorcall *)(int,int))'
note: while trying to match the argument list '(foo::<lambda_717fca3fc65510deea10bc47e2b06be4>, foo::<lambda_f6cd18702c42f6cd636bfee362b37033>)'

另一种情况是,在/Zc:ternal下会遇到错误的是条件运算符,其中只有一个参数的类型是void(而另一个不是throw表达式)。在我们修复源代码的经验中,这些代码的一个常见用法是在ASSERT-like宏中:

void myassert(const char* text, const char* file, int line);
#define ASSERT(ex) (void)((ex) ? 0 : myassert(#ex, __FILE__, __LINE__))

error C3447: third operand to the conditional operator ?: is of type 'void', but the second operand is neither a throw-expression nor of type 'void'

典型的解决方案是用void()简单地替换non void参数。

与/Zc相关的一个更大的问题来源:三元可能来自于在模板元编程中使用条件运算符,因为在这个开关下,某些结果类型会发生变化。以下示例演示在非元编程上下文中条件表达式结果类型的更改:

char a = 'A';
const char b = 'B';
decltype(auto) x = cond ? a : b; // char without, const char& with /Zc:ternary
const char(&z)[2] = argc > 3 ? "A" : "B"; // const char* without /Zc:ternary

在这种情况下,典型的解决方案是在需要的地方在结果类型的顶部应用std::removeu引用特性,以便保留旧的行为。

最后

你今天可以通过下载来尝试这些改进 Visual Studio 2017 15.3.0预览版 . 一如既往,我们欢迎您的反馈——这有助于我们确定工作的优先顺序,同时社区其他成员也在解决类似问题。欢迎通过电子邮件发送任何评论visualcpp@microsoft.com,推特 @视觉 ,或Facebook Microsoft Visual Cpp . 如果您还没有这样做,请检查我们的 上一个职位 在本系列中,我们将介绍改进编译器诊断的进展。

如果您在VS 2017遇到MSVC的其他问题,请通过 报告问题 选项,从安装程序或VisualStudioIDE本身。如需建议,请告知我们 用户语音 .

谢谢您!尤里

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享