如何使用C++20解决一系列运行时错误

如何使用C++20解决一系列运行时错误

作者:BlogUpdater |  时间:2022-01-17 |  浏览:247 |  评论已关闭 条评论

C++20已经发布了一阵子了,并已经被MSVC v16.11及以上版本所支持。今天的文章的主题,不是讲如何使用它,而是讲我们作为编译器开发人员,是如何借助C++20来有效地清除一些列运行时错误。不多啰嗦,直接上东西。

最初的做法
在设计编译器时,你需要考虑的第一件事就是需要提醒开发者他的代码是否有错误或者潜在的非预期行为。在MSVC编译器的实现中,我们采用了如下图所示的错误模型:

上图中的error函数确实能正常工作,因为每个错误代码(ErrorNumber)都有一个对应的错误字符串,用来提示给开发人员,字符串的对应关系千差万别,可以是简单的如 C2056 -> “illegal expression”,到复杂的如 C7627 -> “‘%1$T’: is not a valid template argument for ‘%2$S'”,但是细心的朋友可能注意到了,错误字符串里的%1$T和%2$S代表什么意思呢?
这些是一些编译器特有的格式说明符,用来向开发者显示比较容易理解的一些特定的结构体类型。

格式说明符是一把双刃剑
我们作为编译器开发者,格式说明符确实提供了很大的灵活性和功能。格式说明符可以更清楚地说明错误触发的原因,并为用户提供有关问题的更多上下文信息。格式说明符的问题在于它们在调用上文中提到的error函数时没有进行类型检查,所以如果我们碰巧参数类型错误或根本没有传递参数,它几乎肯定会在稍后出现运行时错误。 当你想要将错误信息字符串重构为更清晰的内容时也会出现其他问题,但要做到这一点,你需要查询该错误消息的每个调用者,并确保这项重构与传递给错误的实际参数保持一致。

在设计一个可以对格式说明符进行检查的系统时,我们有三个高层目标:
1. 验证在编译时传递到我们的诊断API的参数类型,以便尽早地发现错误。
2. 尽量减少对诊断API调用者所做的更改。这是为了确保格式良好的调用保留其原始结构(也不会中断未来的调用)。
3. 最小化对被调用者的实现细节所做的更改。我们不应该在运行时改变诊断程序的行为。

当然,后来的C++标准引入了一些解决方案,可以帮助解决这个问题。一方面,一旦将可变参数模板引入语言中,我们可以尝试一些模板元编程来尝试对错误调用进行类型检查,但这需要单独的查找表,因为constexpr和模板的功能有限。C++14/17都对constexpr和非类型模板参数进行了很多改进。类似下图中的一种做法就比较有用:

因此,我们最终找到了一个工具用来在编译期检查格式说明符的正确性。但是还有一个问题没有解决:我们仍然没有办法静默地检查所有现有的错误调用,这意味着我们必须在错误调用点之间添加一个额外的间接层,以确保ErrorNumber可以在编译时获取字符串并检查对应的参数类型。
在C++17中,下面的代码不会正常工作:

我们不能让error函数本身成为constexpr,因为它做了很多对constexpr不友好的事情。 此外,将所有调用站点调整为以下内容:error(a, b, c) 以便我们可以将错误号作为编译时表达式进行检查,这是令人讨厌的,并且会在编译器中导致大量不必要的折腾。

救世主来了:C++20
C++20 为我们引入了一个重要的工具来启用编译时检查,即consteval。 consteval属于 constexpr家族,但该语言保证用consteval修饰的函数可以在编译时得到评估。一个名为fmtlib 的著名库引入了编译时检查作为核心API的一部分,并且它在不更改任何调用点的情况下这样做,假设调用点根据库的格式正确。想象一下fmt的简化版本,如下图所示:

意图是格式应始终等于”有效”,而T应始终为int。 在这种情况下,根据库,main中的代码格式不正确,但在编译时没有任何验证。 fmtlib使用用户定义类型的一个小技巧完成了编译时检查:

注意:你需要使用std::type_identity_t技巧来防止检查参与类型推导。 我们只希望它推断出其余的参数并将其推断的类型用作Checker的模板参数。

将以上技术汇总起来
上面的代码很强大,因为它为我们提供了一个工具,可以在不更改任何格式良好的调用者的情况下执行额外的安全检查。使用上述技术,我们将编译时检查应用于所有错误、警告和消息处理例程。编译器中使用的代码几乎与上面的fmt相同,只是Checker的参数是一个ErrorNumber。

我们总共确定了大约120个实例,其中我们将错误数量的参数传递给诊断API,或者我们为特定格式说明符传递了错误的类型。多年来,我们收到了关于在发出诊断或直接ICE(内部编译器错误)时奇怪的编译器行为的错误,因为格式说明符正在寻找不正确或不存在的参数。使用C++20,我们在很大程度上消除了未来发生此类错误的可能性,同时为我们提供了安全重构诊断消息的能力,这可以通过一个小关键字实现:consteval。

总结
宇宙的尽头是,C++模板。

最后
Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。
本文来自:《How we used C++20 to eliminate an entire class of runtime bugs》

最近我写了个东西
正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。

标签:

评论已关闭。