C++20特性观摩:Conditionally Trivial Special Member Functions
老伙计们,又来新玩意儿了
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》
相关推荐
- 致C++开发者:Codespaces更新
- Posted on 09月23日
- 深度理解:VMware中Bridge和NAT的使用方法及注意事项
- Posted on 06月27日
- 绘制菜单符号的技法
- Posted on 12月26日
- 解惑之所谓的最大2GB可用虚存空间
- Posted on 09月10日
评论已关闭。