STL或Qt容器?
使用Qt容器( QMap
, QVector
等)对STL等价物有什么优缺点?
我可以看到一个更喜欢Qt的理由:
- Qt容器可以传递给Qt的其他部分。 例如,它们可以用来填充一个
QVariant
然后一个QSettings
(尽pipe只有QList
和QMap
/QHash
的键被string接受)。
还有其他的吗?
编辑 :假设应用程序已经依赖于Qt。
我开始使用std::( w)string和STL容器,并转换为/从Qt等价物,但我已经切换到QString,我发现我正在使用Qt的容器越来越多。
说到string,与std :: basic_string相比,QString提供了更完整的function,并且它完全可以识别unicode。 它也提供了一个高效的COW实现 ,这是我严重依赖的。
Qt的容器:
- 提供与QString相同的COW实现,当使用Qt的foreachmacros(复制副本)以及使用元types或信号和插槽时,这非常有用。
- 可以使用STL风格的迭代器或Java风格的迭代器
- 用QDataStreamstream式传输
- 被广泛用于Qt的API
- 跨操作系统有稳定的实现。 STL实现必须遵守C ++标准,但是可以自由地按照自己的意愿去做(参见std :: string COW争议)。 有些STL实现特别糟糕。
- 提供散列,除非使用TR1,否则散列是不可用的
QTL的理念与STL有所不同,J. Blanchette对其进行了很好的总结 :“STL的容器针对原始速度进行了优化,Qt的容器类经过精心devise,提供了方便,最小的内存使用量和最小的代码扩展。
上面的链接提供了有关QTL实现的更多细节以及使用了哪些优化。
这是一个难以回答的问题。 它可以真正归结为一个哲学/主观的论点。
话虽如此…
我build议“罗马时代…做罗马人做”
这意味着如果你在Qt的土地上,像Qt'ians一样的代码。 这不仅仅是为了可读性/一致性问题。 考虑一下,如果你把所有东西都存放在一个stl容器中,那么你必须把所有的数据都传递给一个Qt函数。 你真的想pipe理一堆将代码复制到Qt容器中的代码吗? 你的代码已经严重依赖于Qt,所以它不像你通过使用stl容器来做更多的“标准”。 什么是容器的要点,如果每次你想使用它的任何有用的东西,你必须将其复制到相应的Qt容器?
Qt容器比STL容器更有限。 几个例子说明STL的优越性(所有这些都是我以前碰到的):
- (Qt 2有
QList
(基于指针)和QValueList
(基于值)); Qt 3有QPtrList
和QValueList
; Qt 4现在有QList
,它跟QPtrList
没什么QPtrList
或QValueList
)。
即使你最终使用Qt容器,使用STL兼容的API子集(即push_back()
,而不是append()
;front()
,不是first()
,…),以避免再次移植Qt 5 。在Qt2-> 3和Qt3-> 4转换中,Qt容器的变化都是需要最多代码stream失的。 - STL双向容器都有
rbegin()
/rend()
,使反向迭代对称前向迭代。 Qt容器没有这样的东西,所以反向迭代是不必要的复杂 。 - STLalgorithm有不同的,但兼容的迭代器types的范围
insert()
,使std::copy()
更less需要。 - STL容器有一个
Allocator
模板参数,与Qt (s/QString/secqstring/
所需的QLineEdit
fork)相比,使自定义内存pipe理变得微不足道 (需要typedef)。 - 没有相当于
std::deque
Qt。 -
std::list
有splice()
。 每当我发现自己使用std::list
,这是因为我需要splice()
。 -
std::stack
,std::queue
正确地聚合它们的底层容器,而不像QStack
,QQueue
那样inheritance它。 -
QSet
就像std::unordered_set
,不像std::set
。 -
QList
是一个奇怪的 。
Qt中很多上面的东西都可以很容易地解决 ,但Qt中的容器库似乎目前缺乏开发重点。
编辑20150106 :花了一些时间试图将C ++ 11支持带到Qt 5容器类之后,我决定这是不值得的工作。 如果你看看C ++标准库实现方面的工作,Qt类将永远赶不上。 现在我们已经发布了Qt 5.4, QVector
仍然没有在重新分配时移动元素,没有emplace_back()
或者rvalue- push_back()
…我们最近也拒绝了QOptional
类模板,等待std::optional
。 同样对于std::unique_ptr
。 我希望这种趋势继续下去。
让我们把这些说法分解成实际可衡量的现象:
- 更轻:Qt容器比STL容器使用更less的内存
- 更安全:Qt容器使用不当的机会较less
- 更容易: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; }
所以,正如我们所看到的,除了一个额外的界面之外,这个界面并没有得到什么好处,除了一个已经圆滑,stream线型和现代化的界面之外。 在已经稳定可用的界面上添加不必要的抽象层次? 不是我的“更容易”的想法。
另外,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风格迭代器中会很容易发生,这要归功于它严重依赖于隐式共享,这是一个在此处记载的反模式,以及其他许多方面 。 在一个越来越多的multithreading框架中使用这个“优化”似乎特别奇怪,但这对你来说是营销。
打火机
这一个有点棘手。 使用“写入时复制”和“隐式共享和增长策略”使得实际上确保您的容器在任何特定时间将使用多less内存非常困难。 这不像STL,它给你强大的algorithm保证。
我们知道向量的浪费空间的最小边界是向量长度的平方根 ,但似乎没有办法在Qt中实现这个; 他们所支持的各种“优化”将排除这一非常重要的节省空间的function。 STL不需要这个特性(而且大多数情况下使用了一倍的增长,这是更浪费的),但是重要的是要注意,如果需要的话,至less可以实现这个特性。
双链表也是如此,它可以使用XOr链接来大幅减less使用的空间。 同样,Qt也是不可能的,因为它对增长和COW有要求。
COW确实可以做出更轻的东西,但是Intrusive Containers也是如此,比如受到boost的支持,而Qt在早期版本中经常使用它们,但是由于它们很难使用,不安全,并且没有太多的用处对程序员。 COW是一个less得多的侵入性解决scheme,但由于上面提出的原因而没有吸引力。
没有理由不能用相同的内存开销或者小于Qt的容器来使用STL容器,而且还有额外的好处,就是在任何时候都知道你将浪费多less内存。 遗憾的是,不可能在原始内存使用方面比较两者,因为这样的基准在不同的用例中会显示出截然不同的结果,这是STLdevise要纠正的确切问题。
结论是
尽可能避免使用Qt容器,而不要施加复制成本,并尽可能使用STLtypes迭代(可能通过包装器或新语法)。
STL容器:
- 有性能保证
- 可以用于STLalgorithm,也有性能保证
- 可以被像Boost这样的第三方C ++库利用
- 是标准的,并可能超越专有的解决scheme
- 鼓励algorithm和数据结构的通用编程。 如果您编写符合STL的新algorithm和数据结构,则可以利用STL已经提供的免费代码。
Qt容器使用copy-on-write成语。
其中一个主要的问题是,Qt的API希望你在Qt的容器中提供数据,所以你可以简单地使用Qt容器,而不是在两者之间来回变换。
另外,如果你已经在使用Qt容器,那么它可能会更优化一些,因为你不需要包含STL头文件,并且可能链接到STL库中。 不过,根据你的工具链,这可能会发生。 纯粹从devise的angular度来看,一致性通常是一件好事。
如果您正在使用的数据主要用于驱动基于Qt的UI,那么肯定使用Qt容器。
如果数据主要在应用程序内部使用,而且您永远不可能摆脱Qt,那么禁止性能问题,请使用Qt容器,因为这会使得传送到UI的数据位更容易处理。
如果数据主要与仅了解STL容器的其他库一起使用,则使用STL容器。 如果遇到这种情况,无论做什么,都会遇到麻烦,因为无论您做什么,都要在容器types之间来回移动。
除了COW之外,STL容器在各种平台上得到更广泛的支持。 如果将工作限制在“主stream”平台上,那么Qt是足够便携的,但是STL也可以在其他更加晦涩的平台上使用(例如德州仪器的DSP)。
由于STL是标准的,而不是由单个公司来控制,所以一般来说,更多的程序员可以轻松地阅读,理解和修改STL代码和更多的资源(书籍,在线论坛,会议等)来支持他们这样比Qt做得更好。 这并不是说为了这个原因,人们应该避开Qt。 除此之外,所有其他的事情都是平等的,你应该默认STL,但是当然,所有的东西都是平等的,所以你必须在你自己的情况下做出最有意义的决定。
关于AlexKR的回答:STL的性能是有保证的,但是一个给定的实现可能会利用平台相关的细节来加速他们的STL。 所以从这个意义上讲,你可能会在不同的平台上得到不同的结果,但它永远不会比明确的保证(模错误)慢。
我的五美分:Qt容器应该在不同的平台上工作。 STL容器依赖于STL的实现。 您可能会得到不同的性能结果。
编辑:我不是说STL是“慢”,但我指出了各种实施细节的影响。
请检查这个 ,也许这个 。
这不是一个真正的STL问题。 显然,如果你在性能上有显着差异,那么在使用STL的代码中就存在问题。
我想这取决于你使用Qt的方式。 如果你把它用在你的产品上,那么使用Qt容器可能是有意义的。 如果仅将其包含在(例如)UI部分中,则最好使用C ++标准容器。
我认为,STL是一个优秀的软件,但是如果我要做一些KDE或Qt相关的编程,那么Qt就是要走的路。 也取决于你正在使用的编译器,而GCC STL工作得很好,但是如果你必须使用SUN Studio CC,那么STL很可能会让你头痛,因为编译器本身不是STL。 在这种情况下,编译器会让你的脑袋受到伤害,只是使用Qt来节省你的麻烦。 只是我2分钱…
QVector有一个(有时)很大的限制。 它只能分配int字节的内存 (注意限制是以字节为单位而不是元素数)。 这意味着试图用QVector分配大于〜2GB的连续内存块将导致崩溃。 这发生在Qt 4和5. std :: vector没有这样的限制。
对于我来说STL容器的主要原因是如果你需要一个自定义的分配器,以便在非常大的容器中重用内存。 假设你有一个存储1000000个条目(键/值对)的QMap。 在Qt中,无论如何,这意味着正是1亿美元的分配( new
调用)。 在STL中,您始终可以创build一个自定义分配器,在内部一次分配所有内存,并在地图填充时将其分配给每个条目。
我的build议是在业务逻辑中编写性能关键algorithm时使用STL容器,然后当结果准备好时,通过UI控件和表单显示(如果需要),将它们转换回Qt容器。