QStringView日记:零分配字符串拆分

2020-06-06 08:49:09

经过四个月的密集开发工作,我很高兴地宣布,第一个QStringTokenizer提交已经在最终成为Qt6.0的版本中实现。医生应该很快就会出现。

虽然Qt中的版本将仅适用于Qt6,但KDAB将为Qt5发布此工具,作为其KDToolBox生产力套件的一部分。是的,这意味着代码不需要C++17,在纯C++11中可以很好地工作。

三年前,当QStringView第一次合并到Qt5.10时,我已经写过,我们不希望在QStringView上有像QString::Split()这样的方法。QStringView完全是关于零内存分配的,Split()返回分配内存的部件(比如QVector)的所属容器。

那么,如果不在容器中,如何返回字符串拆分的结果呢?您可以从C++20的std::range中得到启示,实现一个Lazy序列。惰性序列类似于容器,不同之处在于它的元素不存储在内存中,而是动态计算。在C++20协同程序术语中,它被称为生成器。

因此,QStringTokenizer是一个令牌生成器,除了它的输入之外,它只保存常量内存。

当我在2017年最初构思QStringTokenizer时,我以为它只会在QStringView上运行,仅此而已。但最后一个示例清楚地表明,它还支持拆分QLatin1String。那件事怎么可能?

这就是C++17的用武之地,Qt6.0将依赖于它。C++17为我们带来了类模板参数推导(CTAD):

这就是我们在上面的例子中使用的。实际上,QStringTokenizer是一个模板,但是模板参数是自动推导出来的。

因此,这就是QStringTokenizer拆分QString和QLatin1Strings的方式:在第一种情况下,它是QStringTokenizer<;QStringView,QChar>;,在第二种情况下,它是QStringTokenizer<;QLatin1String,QChar>;。但是请注意:您永远不应该自己显式地指定模板参数,因为您很可能会弄错,因为它们是微妙的和非直观的。只要让编译器完成它的工作就行了。或者,如果您还不能依赖C++17,您可以使用工厂函数qTokenize():

如果QStringTokenizer只是对QStringView或QLatin1String进行操作,则会发生以下情况:__range变量使QStringTokenizer对象在整个for循环中保持活动状态(ok!),但是从widget->;text()返回的临时信息在第3行甚至在我们进入for循环之前就已经销毁了(OOPS)。

这是不可取的,但我们能做些什么来反对它呢?解决方案既简单又复杂:检测临时变量并将其存储在记号赋值器中。

是的,您听到的没错:如果您将拥有容器的临时(“rvalue”)传递给QStringTokenizer,该对象将包含一个副本(如果可能的话,从参数中移出),以将字符串的生存期扩展到QStringTokenizer本身的生存期。

既然我们已经开发了这项技术,我们非常期待它也能在Qt6.0 for QStringBuilder中使用。

在Qt6.0中,我们预计QStringTokenizer还可以将当时可用的QUtf8StringView处理为大海捞针,以及将QRegularExpression和std::boyer_moore_searcher和std::boyer_moore_horspool_searcher处理为针。我们还可以在支持它们的编译器上将其重新实现为C++20协程,这取决于我们将从中获得多大的性能。

QStringTokenizer通用且安全地拆分字符串,内存分配为零。现在就从KDToolBox免费获取它,您可以着眼于Qt6来验证您的代码的未来。