2020年9月30日(星期三)格卢别夫 | 评论意见

使用Qt 6,许多组件都会发生更改。集装箱也不例外。在这篇博客文章中,我试图捕捉对QList和相关类最重要的更改。

QVector和QList是统一的

在此之前,Qt为这两个容器提供了非常不同的实现:QVector是一个自然而直观的类似数组的容器,而QList在其实现中非常特殊,以很好地适应Qt本身定义和使用的类型。通过对现有类型的Qt 6更新,在以前版本中已经完成的工作的支持下,类之间的实现差异似乎没有什么好处。此外,在许多情况下,QVector被证明是一个更好的选择。

因此,在Qt 6中,QVector和QList是统一的,并以QVectory模型作为底层实现。对于更高级的框架用户来说,这意味着QT5 QList对泛型类型的额外间接级别已经消失,元素总是直接存储在分配的内存中。

然而,我们决定QList应该是真正的类,而QVector应该只是QList的别名。这样做的目的是:

  • 简化移植工作,因为许多QTAPI使用QList而不是QVector.我们认为用户代码也是如此,至少在与Qt的交互级别上是这样
  • 明确说明QVector2D和朋友与QVector2D无关
  • 与QStringList和QByteArrayList命名约定同步

快速准备

当QList的新实现建立在QVectoration的基础上时,我们发现了一个重要的用例:Q6之前,QList在开始时有一个已摊销的恒定插入时间(即a)。)。为了解决这个属性,Qt 6中的QList也支持优化的前置,为了更好地对齐容器实现,QString和QByteArray也是如此。

不幸的是,一切都是有代价的。在这种情况下,价格是跨容器修改操作的迭代器有效性。与Qt5中的QVectori不同,Q6中的QList(因此也是QVectori)允许在操作之间保持迭代器有效的自由度较小。根据经验,考虑到添加元素或从容器中移除元素的任何操作都会使“记住的”迭代器无效,即使QList不是隐式共享的。有关此规则的更多详细信息和每个函数的异常,请参见Qt6文档快照,随着文档不断更新,以更好地反映当前的状况。

QList可能会因元素的删除而收缩。

通常,QList自动管理其内存。在成长时,它预先分配多余的内存以解决进一步增长的问题。对称地说,我们希望QList在体积减小时减少占用的空间,特别是当元素删除留下大量未使用的空间时。缩小到较小的容量会降低内存占用,这在某些用例中是一个很好的特性。这种功能已经是Qt 5之后的一个特性请求,但是,由于兼容性原因,我们以前无法更新代码,因为我们的用户依赖于现有的行为。

默认情况下方便,这种内存管理机制也可能变得效率低下,例如,足够长的添加序列会导致重复重新分配和额外的元素复制。同样,经常性的清除会导致大量的收缩。在这种情况下,增加和清除费用是可以避免的。

QList::Reserve()是一种可以用来减少重新分配频率并提示QList尽可能长时间保持现有内存容量的方法。当向QList添加元素时,预先保留(并因此分配)已知大小的函数(例如,预置、追加、插入)将归结为仅复制新数据。类似地,调用Reserve()(例如,以当前大小作为输入参数)将使下列清除避免减少容量。

在某种程度上,Reserve()允许干预QList的内存管理。要恢复自动行为,您应该调用qList::press()。这将使所分配的空间精确地调整为存储的元素的数量,并提示QList不再需要当前内存容量来保存。但是,请注意,如果您的数据使用时间很短,调用挤压()(可能会重新分配内存)可能会付出很大的代价。等待QList被销毁可能更容易(也更快)。

QList的记忆不受2GiB的限制

在Qt 6之前,QList仅限于使用最多2GiB的内存。如今64位架构已经占据了强大的主导地位,对于那些希望使用更多空间来完成任务的用户来说,这是一个不必要的障碍。

在Qt 6中,对QList的底层大小类型进行了更改以解决这一问题,并且可以创建QList来分配更大的内存(当然是在系统提供的范围内)。因此,所有QList方法也被更改为与此对齐,现在使用qsizetype,而不是int。

作为一个缺点,用户很可能会看到他们的编译器警告缩小转换。考虑下面的例子。

Qt5中完全正确的代码:

void myFunc(QList<MyType> data) {
    int size = data.size();
    // ...
}

...由于从qsizetype转换到int,会在Qt 6中得到编译器警告。一个自然的解决方案是使用自动关键字而不是特定类型。当您希望同时针对Qt5和Qt6构建时,这也是一种补救方法。

其他较小的变动

我们还简化了QStringList,它现在是QList<QString>的别名,而不是Qt 5版本,QStringList是从QList<QString>派生的一个不同的类。仔细的读者可能会注意到,QStringList过去有一些在QList中不可用的特殊方法,例如QStringList::Join()。这些特定于QStringList的方法仍然可用,但与QByteArrayList的实现方式类似,它们被放入QList<QString>中。

 

正如您可能知道的,QList实现利用了它的元素类型的附加特性,这是通过使用Q_DECLARE_TYPEINFO()宏提供的。某些类型的特性允许使用更快的算法来提高“开箱即用”的性能。我们简化了这一机制:

  • 可移动型和可重定位型现在意味着同样的事情。由于C++11引入了“可移动”的含义,这并不完全是q_moble_type的意思(和意思),所以我们鼓励客户端代码使用q_relocable_type。
  • 微不足道的可复制性微不足道的可毁性类型不再需要标记为可重定位。它们将被自动检测到。

在任何情况下,当对类型有疑问时,您总是可以在编译时检查和确保特定的特性。例如:

static_assert(QTypeInfo<MyType>::isRelocatable);  // makes sure MyType is relocatable

您可以在我们的文档快照.

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐