C++20特性观摩:Conditionally Trivial Special Member Functions

C++20特性观摩:Conditionally Trivial Special Member Functions

作者:BlogUpdater |  时间:2020-11-24 |  浏览:1184 |  评论已关闭 条评论

老伙计们,又来新玩意儿了
C++标准委员会目前正在致力于添加一些新功能到语言中,以实现简化C++代码的战略目标。
让我们举其中的一个小例子:Conditionally Trivial Special Member Functions,
我们在Visual Studio 2019版本16.8中添加了对它的支持。
除非你深入地了解高性能库开发时候所面临的繁琐工作,否则它的好处并不是那么显而易见。
因此,我写了这篇文章来展示如何在不需要大量模板代码的情况下,使某些泛型类型更加高效。

所面临的问题
在C++的世界中,用来包装其他类型的类型很常见:pairs, tuples, optionals, adapters等。
对于其中的某些类型,你可能不能使用它们默认的实现,例如:默认构造函数,复制/移动构造函数,复制/移动赋值,析构函数等,因为还有一些其他工作需要完成。让我们看看下面这个std::optional-like类型的例子:

它具有一个bool成员指示当前是否已经存储了一个值,以及一个union成员,它在可选值为空时存储一个值或存储一个字符。
默认的特殊成员函数在这里不起作用:当联合成员具有非默认的构造函数和析构函数时,我们需要以可选类型显式处理它们。如果我们向实现拷贝构造函数,则下面的代码是一个可能的实现:

我们检查rhs是否具有值,如果存在,则使用它来复制构造我们自己的值。
但是这里有一个性能问题,假设我们制作了一个可选的的副本,如下所示:

由于int可以简单地进行复制构造(即可以通过复制其内存来复制它们,而不必使用任何构造函数),因此复制optional 仅应复制其字节表示形式。但这是编译器为make_copy生成的代码是这样的:

我们真正想要的是一种方法,如果T中对应的成员很小,则使用默认的特殊成员函数,否则使用我们的自定义成员函数。

C++17中的做法
起初似乎可行的一种方法是使用std::enable_if在默认和自定义拷贝构造函数实现之间进行选择,选择的结果具体取决于T的属性,如下图所示:

不幸的是,默认构造函数以外的特殊成员不能是模板,所以这是行不通的。
起作用的常见解决方案是:将模板的存储和特殊成员分割为基类,然后通过检查相关的类型特征来选择要从中继承的模板。这项工作实在太繁琐了,因此在这篇文章的底部,同时我对它做了一些解释。

如果我们进行更改,那么make_copy的汇编代码将变成这样:

现在,我们已经生成了更有效的代码,但有大量棘手的C++代码,这些代码很难编写,维护和有效地编译。C++20使我们能够保持高效的汇编,并大大简化了代码。

C++20提出的方案
尽管我们上面的std::enable_if解决方案由于这些函数不能用作模板而无法使用,但是你可以使用C ++20概念来约束非模板函数,如下所示:

现在,当且仅当T为T时,optional可以很容易地复制构造,并且具有最小的模板使用度。
我们拥有高效的汇编代码生成和C++代码,它们比以前更易于理解和维护。

蹩脚的C++17实现
如所承诺的,下面我会简单的介绍下C++17中的操作方法。
我们首先将存储分成自己的基类:

然后,我们为T的默认复制构造提供了复制构造函数的基类,并引入了默认模板参数,稍后我们将对其进行专门介绍。

然后我们专门针对T不是默认的可复制构造时的模板:

然后,我们从optional_copy_base 进行可选继承:

然后,我们再次对move构造函数,析构函数,拷贝赋值和move赋值运算符执行此操作。
这正是标准库实现者为了获得最佳代码源而必须经历的事情,而付出的代价是实现和维护负担。
相信我,这绝不是一件好玩的事情。

总结
什么?模板?
告辞!

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

标签:

评论已关闭。