Qt将
QList
称为“万能工具”,但这句话的另一半是“样样皆通,样样不精”。如果您计划在列表的两端添加内容,并且它们不大于指针大小,那么我认为
QList
是一个很好的选择,因为
QList
会在列表前后保留空间。就好像这些就是使用
QList
的好理由。
QList
会自动将“大”对象存储为指针并在堆上分配对象,这可能是一个好事情,如果您是一个不知道如何声明
QVector<T*>
并使用动态分配的新手。这不一定是好事,在某些情况下,它只会膨胀内存使用量并增加额外的间接性。我认为明确表达自己的意图总是一个好主意,无论是指针还是实例。即使您确实想要堆分配,也最好自己分配,然后将指针添加到列表中,而不是构造对象,然后在堆上复制构造对象。
在许多地方,Qt会返回一个带有开销的
QList
,例如获取一个
QObject
的子项或搜索子项时。在这种情况下,使用一个在第一个元素之前分配空间的容器是没有意义的,因为它是已经存在的对象列表,而不是您可能要添加到前面的内容。我也不太喜欢缺少
resize()
方法。
想象一下,您有一个大小为9个字节且在64位系统上具有字节对齐的对象。这对于
QList
来说“太大了”,因此它将使用8字节指针+慢速堆分配的CPU开销+堆分配的内存开销。它将使用两倍的内存,并带有额外的间接性访问,很难像广告中那样提供性能优势。
至于为什么
QVector
不能突然成为“默认”容器 - 在比赛中你不会换马 - 这是一个遗留问题,Qt是一个如此古老的框架,即使许多东西已被弃用,更改广泛使用的默认值并不总是可能的,不会破坏大量代码或产生不希望的行为。无论好坏,
QList
都可能继续成为默认容器,直到Qt 5结束,很可能在下一个主要版本中也是如此。这也是Qt将继续使用“愚蠢”指针的原因,尽管智能指针已成为必需品,每个人都在抱怨普通指针有多糟糕,永远不应该使用它们。
话虽如此,没有人强迫您在设计中使用QList。没有理由不将QVector作为默认容器。我自己并没有在任何地方使用QList,在返回QList的Qt函数中,我仅将其用作临时变量以将内容移动到QVector中。
此外,这只是我的个人观点,但我发现Qt中有很多设计决策在性能、内存使用效率或易用性方面并不合理。总的来说,有很多框架和语言喜欢推广他们的做事方式,不是因为这是最好的方法,而是因为这是他们的做事方式。
最后:
对于大多数情况,QList是正确的类。
这实际上取决于你对此的理解。在这种情况下,我认为“正确的”并不代表“最好的”或“最优的”,而是代表“足够好”,即“它可以胜任,即使不是最好的”。特别是如果你不知道不同的容器类及其工作原理。
归纳一下:
QList的优点:
- 您打算在列表前置对象大小不超过指针大小时使用,因为它会在前面保留一些空间。
- 您打算在列表中间插入对象(实质上)比指针大得多的对象(我在这里非常慷慨,因为您可以轻松地使用显式指针的QVector来实现相同且更便宜的操作——没有额外的复制),因为调整列表大小时,不会移动任何对象,只有指针。
QList的缺点:
- 没有resize()方法,reserve()是一个微妙的陷阱,因为它不会增加有效的列表大小,即使索引访问起作用,它也属于UB类别,也无法迭代该列表。
- 当对象大于指针时,会进行额外的复制和堆分配,如果对象标识很重要,这也可能是一个问题。
- 使用额外的间接访问比指针大的对象。
- 由于最后两个原因,具有CPU时间和内存使用开销,同时更少的缓存友好性。
- 当用作“搜索”返回值时,会带来额外的开销,因为您不太可能在其前面或后面添加内容。
- 只有在必须使用索引访问时才有意义,对于最佳的前置和插入性能,链表可能是更好的选择。
CONs略微超过PROs,这意味着在“非正式”使用中,
QList
可能是可以接受的,但在CPU时间和/或内存使用量是关键因素的情况下,绝对不要使用它。总的来说,
QList
最适合懒惰和粗心的使用,当您不想考虑用例的最佳存储容器时,通常会使用
QVector<T>
、
QVector<T*>
或
QLinkedList
(我排除了“STL”容器,因为我们在谈论Qt,Qt容器同样具有可移植性,有时更快,并且肯定更易于使用和更清晰,而
std
容器则是不必要的冗长)。