STL或Qt容器?


185

使用Qt容器的优点和缺点(QMapQVector在他们的STL等价物,等等)?

我可以看到一个偏爱Qt的原因:

  • Qt容器可以传递到Qt的其他部分。例如,可以使用它们先填充a QVariant,然后填充a QSettings(尽管有一定限制,但仅接受QListQMap/且QHash其键为字符串)。

还有其他吗?

编辑:假设应用程序已经依赖Qt。

Answers:


135

我开始只使用std::(w)stringSTL容器并与Qt等效项进行相互转换,但是我已经切换到QStringQt的容器,并且发现我越来越多地使用Qt的容器。

在字符串方面,与之QString相比std::basic_string,它提供了更完善的功能,并且完全支持Unicode。它还提供了有效的COW实施,我已经高度依赖它了。

Qt的容器:

  • 提供与中相同的COW实现QString,这在使用Qt的foreach宏(执行复制操作)以及使用元类型或信号和插槽时非常有用。
  • 可以使用STL样式的迭代器或Java样式的迭代器
  • 可与 QDataStream
  • 在Qt的API中广泛使用
  • 具有跨操作系统的稳定实现。STL实现必须遵守C ++标准,但可以自由地随意进行(请参见std::string COW争议)。一些STL实现特别糟糕。
  • 提供散列,除非您使用TR1,否则这些散列是不可用的

QTL与STL有不同的理念,J。Blanchette对此进行了很好的总结:“虽然STL的容器针对原始速度进行了优化,但是Qt的容器类经过精心设计,以提供便利,最小的内存使用和最小的代码扩展。”
上面的链接提供了有关QTL实施以及使用了哪些优化的更多详细信息。


12
在新标准中,c ++ 0x COW几乎不存在。

16
回复:“精心设计以提供最小的内存使用量”。您不应该相信营销。QList<double>32位体系结构的概要文件,供内存使用,供您自己查看。
Marc Mutz-mmutz,2011年

11
“它还提供了有效的COW实现”:在多线程应用程序方面,COW并不是那么高效……
Grizzly

5
@ MarcMutz-mmutz尝试配置QVector而不是QList。Qt有一个很好的解释,即QList旨在将指针存储在对象上。因此,动态创建的每个双项和指向该项的指针都存储到中QList。QList被设计为向量和链接列表之间的“中间”容器。它不是设计用于内存/性能至关重要的情况。
德米特里·索佐诺夫

2
@ user1095108没问题。去使用stl。我们中有些人喜欢快速编写正确的代码。这也没有错。
weberc2 2014年

178

这是一个很难回答的问题。它确实可以归结为哲学/主观论点。

话虽如此...

我建议规则“在罗马时...像罗马人一样做”

这意味着如果您在Qt国度,请像Qt'ians一样进行编码。这不仅是出于可读性/一致性方面的考虑。考虑一下如果将所有内容存储在stl容器中会发生什么,那么必须将所有数据传递给Qt函数。您是否真的要管理一堆将事物复制到Qt容器中/从其中复制出来的代码。您的代码已经非常依赖Qt了,因此它并不像您通过使用stl容器使其变得更“标准”一样。容器的意义何在?如果您每次想将其用于有用的东西时,都必须将其复制到相应的Qt容器中?


1
+1完全正确,这就是我试图在我的问题中解释的内容(“我可以看到偏爱Qt的一个原因”),因此我对其进行了一些编辑。谢谢
Julien-L

绝对好说。如果您要进行QT,请使用QT!想象一下,维护人员打开QT应用程序并看到QT和STL可以互换使用时的“ WTF”时刻。那可能最终成为一场(不必要的)噩梦。
2014年

5
@ It'sPete STL是标准的一部分;QT不是。任何使用该标准的代码都不应触发“ WTF”时刻。
爱丽丝

5
罗马人将俘虏放进罗马竞技场,然后用狮子追捕他们。如果您了解得更多,请不要遵循当地的习惯。Qt中的情况与罗马帝国的《现代人》中的情况一样……
Marc Mutz-mmutz 2015年

1
@mmutz,您说那是一件坏事,我想放一些我在罗马斗兽场中找到的代码并观看表演
slf

64

Qt容器比STL容器更受限制。STL优越的几个例子(我过去曾提到过的所有例子):

  • STL是标准化的,不符合所有的Qt版本中改变(QT 2例QList(指针型)和QValueList(价值为基础); Qt的3例QPtrListQValueList; Qt的4现在有QList,而且它的什么都没有像QPtrList QValueList)。
    即使最终使用了Qt容器,也请使用与STL兼容的API子集(即push_back(),不要append(); front()first()也要,...),以避免再次移植Qt5。在Qt2-> 3和Qt3-> 4中转换中,Qt容器中的更改属于需要代码量最大的更改。
  • STL双向容器都具有rbegin()/ rend(),使得反向迭代对称于正向迭代。并非所有Qt容器都具有它们(关联的容器没有),因此反向迭代不必要地复杂。
  • STL容器具有insert()不同的但兼容的迭代器类型,从而std::copy()减少了使用的频率。
  • STL容器有一个Allocator模板参数,使得自定义的内存管理微不足道(typedef的要求),比的Qt(叉QLineEdit需要s/QString/secqstring/)。EDIT 20171220:这切断了Qt,使之不再遵循C ++ 11和C ++ 17之后的分配器设计的进步,请参见。例如John Lakos的演讲第2部分)。
  • 没有等效于的Qt std::deque
  • std::listsplice()。每当我发现自己使用时std::list,是因为我需要splice()
  • std::stackstd::queue正确汇集其基本容器,不继承它,因为QStackQQueue这样做。
  • QSet就像std::unordered_set,不喜欢std::set
  • QList只是奇怪

在Qt中可以很容易地解决上述许多问题,但是Qt的容器库目前似乎缺乏开发重点。

EDIT 20150106:在花了一些时间尝试将C ++ 11支持引入Qt 5容器类之后,我认为这是不值得的。如果您看一下正在投入C ++标准库实现的工作,那么很明显Qt类将永远不会追上。我们现在已经发布了Qt 5.4,并且QVector 仍然不会在重新分配上移动元素,不具有emplace_back()或不包含右值push_back()-...我们最近还拒绝了QOptional类模板,std::optional而是在等待。同样适用于std::unique_ptr。我希望这种趋势继续下去。


3
嗯 我当时的印象QList 相当于std::deque。显然,我不应该只浏览文档。
丹尼斯·齐克福斯

QVector不得不crbegin因为Qt的5.6和朋友
巴特Louwers

@Alex:是的,我添加了简单的容器,但是并不是所有的Qt容器都具有它们(因为您不使用std::reverse_iterator损坏的QHash/ QMap迭代器,当它被取消引用时,返回mapped_type而不是value_type)。没有什么不能解决的,但是请看我从2015
。– Marc Mutz-mmutz

@ MarcMutz-mmutz谢谢您的澄清。
巴特·卢维尔

可能值得将例如QVector使用int它作为索引的事实添加到列表中,从而限制了31位的大小(即使在64位系统上)。而且,它甚至不能存储INT_MAX大小大于1个字节的元素。例如.size()QVector<float>在x86_64 Linux gcc上我可以拥有的最大容量是536870907个元素(2²⁹-5),而std::vector<float>成功分配了4294967295个元素(2³²-1;由于缺少RAM,因此没有做更多的尝试(此大小已经占用了16 GiB) )。
罗斯兰

31

让我们将这些主张分解为实际的可衡量现象:

  • 更轻:Qt容器比STL容器使用更少的内存
  • 更安全:Qt容器被不当使用的机会更少
  • 更容易:Qt容器带来的知识负担更少

更轻松

在这种情况下,Java风格的迭代比STL风格更“容易”,因此Qt易于使用,因为它具有附加的接口。

Java风格:

QListIterator<QString> i(list);
while (i.hasNext())
    qDebug() << i.next();

STL样式:

QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
    qDebug << *i;

Java迭代器样式的好处是体积更小,更干净。问题是,这实际上不再是STL样式。

C ++ 11 STL样式

for( auto i = list.begin(); i != list.end(); ++i)
    qDebug << *i;

要么

C ++ 11 foreach样式

for (QString i : list)
    qDebug << i;

这是如此简单,以至于没有理由使用其他任何东西(除非您不支持C ++ 11)。

但是,我最喜欢的是:

BOOST_FOREACH(QString i, list)
{
    qDebug << i;
}

因此,正如我们所看到的那样,除了一个光滑,流线型和现代的界面之外,该界面除了其他界面之外,对我们没有任何帮助。在已经稳定且可用的接口之上添加不必要的抽象级别?不是我的“更轻松”的想法。

同样,Qt foreach和java接口会增加开销。它们复制结构,并提供不必要的间接级别。这看起来似乎不多,但是为什么要增加一层开销以提供一个不是那么简单的界面呢?Java具有此接口,因为Java没有运算符重载;C ++可以。

更安全

Qt给出的理由是隐式共享问题,它既不是隐式也不是问题。但是,它确实涉及共享。

QVector<int> a, b;
a.resize(100000); // make a big vector filled with 0.

QVector<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
Now we should be careful with iterator i since it will point to shared data
If we do *i = 4 then we would change the shared instance (both vectors)
The behavior differs from STL containers. Avoid doing such things in Qt.
*/

首先,这不是隐式的。您正在将一个向量明确分配给另一向量。STL迭代器规范明确指出了迭代器属于容器,因此我们已经明确引入了b和a之间的共享容器。其次,这不是问题。只要遵循迭代器规范的所有规则,就绝对不会出错。唯一出问题的地方是:

b.clear(); // Now the iterator i is completely invalid.

Qt将此表示为某种含义,例如从此场景重新出现问题。没有。迭代器是无效的,就像可以从多个不相交区域访问的任何东西一样,这就是它的工作方式。事实上,这将很容易与Qt中的Java风格的迭代器它是沉重的隐含共享,这是记录在案的反模式的依赖出现,感谢在这里,并在许多其他领域。将这种“优化”用于越来越多的向多线程的框架中似乎很奇怪,但这对您来说是行销。

打火机

这个有点棘手。使用写时复制和隐式共享与增长策略使实际上很难保证容器在任何给定时间将使用多少内存。这与STL不同,后者为您提供强大的算法保证。

我们知道向量浪费空间的最小边界是向量长度的平方根,但是似乎没有办法在Qt中实现。他们支持的各种“优化”将排除此非常重要的节省空间功能。STL不需要此功能(大多数使用STL增长一倍,这更浪费),但是请务必注意,如果需要,您至少可以实现此功能。

双链列表也是如此,它可以使用XOr链接来大大减少所用的空间。同样,由于Qt的增长和COW要求,这是不可能的。

COW确实可以减轻重量,但侵入容器(例如由boost支持的容器)和Qt可以在早期版本中频繁使用,但是由于它们难以使用,不安全并且会带来负担,因此不再使用它们了在程序员身上。COW是一种不太麻烦的解决方案,但由于上述原因而没有吸引力。

没有理由为什么您不能使用与Qt的容器相同或更少的内存成本的STL容器,而额外的好处是实际上知道您在任何给定时间将浪费多少内存。不幸的是,不可能在原始内存使用率上将两者进行比较,因为这样的基准测试在不同的用例中会显示出截然不同的结果,这正是STL旨在纠正的确切问题。

结论

尽可能避免使用Qt容器而不增加复制成本,并尽可能使用STL类型迭代(可能通过包装器或新语法)。


4
您的观点在很大程度上是有效的,但其中存在一些误导性信息:Adding an unnecessary level of abstraction on top of an already stable and usable interface? Not my idea of "easier".Qt的Java样式的迭代器未添加到C ++ 11中;他们早于它。无论如何,Qt foreach(QString elem, list)与C ++ 11的foreach或BOOST_FOREACH一样容易,并且可以与C ++ 11之前的兼容编译器一起使用。
weberc2 2014年

@ weberc2您感到困惑;Qt的Java样式迭代器添加在C ++(不是C ++ 11)迭代器之上。这是使接口肿的附加抽象层(并且是不必要的抽象层),这并不容易。而且Qt的foreach不像BOOST_FOREACH那样容易,因为它明显不那么安全,并且没有同样广泛的支持(BOOST_FOREACH可以适用于任何范围,适用于任何版本的C ++,因为QT中的foreach要求C + +03规范)。不惜一切代价避免QT的foreach。
爱丽丝

So, as we can see, this interface gains us nothing except an additional interface, *on top of* an already sleek, streamlined, and modern interface. Adding an unnecessary level of abstraction on top of an already stable and usable interface? Not my idea of "easier".(重点是我)您在向我们展示了foreach的C ++ 11和BOOST版本之后说了这一点,这听起来像Qt版本是基于这两个版本之一构建的,而AFAICT并非如此。我确定这不是您的意思,但这就是它的实现方式。因此是“误导性信息”。
weberc2 2014年

It's an additional layer of abstraction (and an unnecessary one) that bloats the interface, which is not easier.尚不清楚您要与之进行比较。C ++ 03迭代器?C ++ 11迭代器?BOOST_FOREACH?上述所有的?
weberc2 2014年

1
我只是说,您所指的是哪种迭代方法通常很模棱两可。我相信您认为自己很明确,语言很合理,但是拒绝指定似乎很奇怪。我想同意不同意。
weberc2 2014年

22

STL容器:

  • 有性能保证
  • 可以用于也具有性能保证的 STL算法
  • 可以被Boost等第三方C ++库利用
  • 是标准的,并且可能会超过专有解决方案
  • 鼓励对算法和数据结构进行通用编程。如果编写符合STL的新算法和数据结构,则可以免费利用STL已经提供的功能。

5
如果您使用STL支持编译Qt(默认设置),则上述所有内容(除标准之外)均适用于QTL。STL支持包括迭代器功能,容器typedef(const_iterator等),转换功能(往返STL)。
rpg

2
Qt不是专有的
txwikinger 2013年

2
@rpg几乎所有的人都不是QTL。QTL没有强大的性能保证(因为它们过去很容易就已经破坏了它们),不符合STL(没有反向,因此不能被大量使用),不是标准的(它们在版本之间不断变化),并且不鼓励通用编程(例如,它们没有分配器的模板参数)。
爱丽丝

15

Qt容器使用写时复制习惯。


2
+1可能是性能和资源上的重大优势
RedGlyph

31
或可能是一个重大的劣势。参见gotw.ca/publications/optimizations.htm
Kaz Dragon,

3
原子引用计数似乎运行得很好:labs.trolltech.com/blogs/2006/10/16/…–
rpg

只要满足其性能保证和规范,STL容器就可以自由使用存在的任何惯用法。即使在C ++ 11 / C ++ 14 STL下,COW也有效。
爱丽丝

1
@Alice COW在大多数情况下不是有效的实现,因为它破坏了标准的复杂性,并且在几乎任何情况下都保证了迭代器的有效性。可以用COW实施的少数几个类之一是std::basic_string,该标准对C ++ 11采取了措施以使其与标准不符。
Tiago Gomes

9

主要问题之一是Qt的API希望您在Qt的容器中提供数据,因此您也可以简单地使用Qt容器而不是在两者之间来回转换。

另外,如果您已经在使用Qt容器,则最好单独使用它们,因为您不必包括STL头文件,也不必在STL库中链接。但是,根据您的工具链,还是可能会发生这种情况。纯粹从设计角度来看,一致性通常是一件好事。


1
在使用STL的实际应用程序中,除非通常大大高估了与Qt的接口,否则您必须在STL和Qt容器之间“来回转换”的速率。大多数时候,您对表示层(使用Qt)进行往返std :: transform,并且免费获得了容器开关。有兴趣的人士可以浏览projects.kde.org/projects/kde/kdepim/repository/revisions/…自己看看。
Marc Mutz-mmutz

8

如果您正在使用的数据主要用于驱动基于Qt的UI,则一定要使用Qt容器。

如果数据大部分是在应用程序内部使用的,并且您永远都不可能离开Qt,那么除非存在性能问题,否则请使用Qt容器,因为这样会使进入UI的数据位更易于处理。

如果数据通常与仅了解STL容器的其他库结合使用,则请使用STL容器。如果遇到这种情况,无论如何都将遇到麻烦,因为无论您做什么,都将在容器类型之间进行大量的来回移植。


7

除了COW的不同之外,STL容器在各种平台上得到了更广泛的支持。如果您将工作限制在“主流”平台上,则Qt具有足够的可移植性,但是STL也可以在许多其他晦涩的平台(例如,德州仪器(TI)的DSP)上使用。

因为STL是标准的,而不是由一个公司控制的,所以通常来说,有更多的程序员可以轻松阅读,理解和修改STL代码以及更多的资源(书籍,在线论坛,会议等)来支持它们。这样做比Qt的要多。这并不是说仅出于这个原因就应该回避Qt。只是,在所有其他条件都相同的情况下,您应该默认使用STL,但是当然所有条件很少相等,因此您必须在自己的上下文中做出决定,这才是最有意义的。

关于AlexKR的答案:STL性能在一定范围内得到保证,但是给定的实现可能利用平台相关的细节来加快其STL。因此,从这个意义上讲,您可能在不同的平台上获得不同的结果,但是它永远不会比显式保证(模块错误)慢。


9
关于您的第一点:我假设OP指的是已经使用Qt的项目,因此已经限于“主流”平台。似乎不太可能有人会为它的容器类引入像Qt这样的重量级库。
ThisSuitIsBlackNot

4

我的5分钱:Qt容器在不同平台上的工作原理类似。而STL容器取决于STL的实现。您可能会获得不同的性能结果。

编辑: 我不是在告诉STL是“慢”,但我指出各种实现细节的影响。
请检查这个,然后也许这个
这不是STL的真正问题。显然,如果您在性能上有显着差异,则使用STL的代码中存在问题。


无论实现如何,STL容器都是相似的。您不能像幕后列表那样实现矢量,因为它必须位于连续的内存块中。通常,在所有主要平台上都会对STL进行最大程度的优化。
Yacoby

1
如果您遵守STL的承诺(而不是假设如何实现),那么在带有STL的平台之间移动就不会有问题。与Qt相同。
迈克尔·科恩

这与true完全相反。STL容器在所有平台上始终相同。如果不这样做,就不是STL。但是,QT会极大地改变版本之间的性能,因此,在具有QT4.0而不是QT4.8的平台上,您可以进行一些重大更改。
爱丽丝2014年

您会混淆两种截然不同的演奏类型;算法性能和实际计算性能。所有STL实现都保证相同的算法性能。如果向量需要log(n)时间来索引元素,则它不是STL向量。您的链接指向实际的计算性能,在此讨论中这是没有意义的。QT在版本之间更改其算法,并且不同平台上的同一C ++获得不同的性能。根据我的经验,这些远比STL性能上的差异更具延展性。
爱丽丝


3

我认为STL是一款出色的软件,但是,如果我要进行一些KDE或Qt相关的编程,那么Qt是必经之路。同样,这取决于您使用的编译器,GCC STL的效果很好,但是如果必须使用SUN Studio CC,那么STL很可能会给您带来麻烦,因为编译器本身不是STL。在这种情况下,由于编译器会伤到您的头,只需使用Qt即可避免麻烦。只是我的2美分...


3

QVector中(有时)有一个很大的限制。它只能分配int字节的内存(请注意,限制以字节为单位,而不是元素数)。这意味着尝试使用QVector分配大于2GB的连续内存块将导致崩溃。Qt 4和5会发生这种情况。std :: vector没有这种限制。


0

对我来说,使用STL容器的主要原因是,如果您需要自定义分配器以便在非常大的容器中重用内存。例如,假设您有一个存储1000000个条目(键/值对)的QMap。在Qt中,new无论如何,这恰好意味着1万亿个分配(调用)。在STL中,您始终可以创建一个自定义分配器,该分配器在内部一次分配所有内存,并在填充映射时将其分配给每个条目。

我的建议是在业务逻辑中编写性能关键算法时使用STL容器,然后在准备好结果时将其转换回Qt容器,并由UI控件和表单显示(如果需要)。


不是要保卫这里的QTL,但你可以专门QMapNode<K,V>为你KV提供你自己的operator new
Marc Mutz-mmutz 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.