使用Qt容器(QMap
,QVector
等)对STL等效项的优缺点是什么?
我可以看到一个更喜欢Qt的理由:
QVariant
,然后填充QSettings
(但有一些限制,只有QList
和QMap
/ QHash
,其键是字符串被接受)。还有其他吗?
编辑:假设应用程序已经依赖于Qt。
答案 0 :(得分:172)
这是一个难以回答的问题。它可以归结为哲学/主观论证。
有人说......
我推荐规则“在罗马时......像罗马人那样做”
这意味着如果你在Qt土地上,代码就像Qt'ians那样。这不仅仅是为了可读性/一致性问题。考虑如果将所有内容存储在stl容器中然后必须将所有数据传递给Qt函数会发生什么。您是否真的想管理一堆将内容复制到Qt容器中的代码。你的代码已经严重依赖于Qt,所以它不像你通过使用stl容器使它更加“标准”。如果每次想要将它用于任何有用的东西时,容器的重点是什么,你必须将它复制到相应的Qt容器中吗?
答案 1 :(得分:125)
我开始只使用std::(w)string
和STL容器并转换为Qt等价物,但我已经切换到QString
并且我发现我正在使用Qt的容器越来越多
对于字符串,与QString
相比,std::basic_string
提供了更完整的功能
完全支持Unicode。它还提供efficient COW implementation,我非常依赖它。
Qt的容器:
QString
相同的COW实现,这对于使用Qt的foreach
宏非常有用
(使用元类型或信号和插槽时使用副本)。QDataStream
std::string
COW争议)。一些STL实现尤其如此
坏。 QTL与STL有不同的理念,J. Blanchette well summarized:“尽管STL的容器针对原始速度进行了优化,但Qt的容器类经过精心设计,以提供方便,最少的内存使用,最小的代码扩展。“
上面的链接提供了有关QTL实现的更多细节以及使用的优化。
答案 2 :(得分:60)
Qt容器比STL容器更受限制。 STL的优势所在的几个例子(我过去曾经遇到的所有这些):
QList
(基于指针)和QValueList
(基于值); Qt 3有QPtrList
和QValueList
; Qt 4现在有QList
,而且它完全没有像QPtrList
或 QValueList
)。push_back()
,而非append()
; front()
,而不是first()
,...)为了避免再次移植Qt 5.在Qt2-> 3和Qt3-> 4转换中,Qt容器中的变化是需要最多代码流失的那些。 rbegin()
/ rend()
,使反向迭代与前向迭代对称。并非所有Qt容器都有它们(关联的容器没有),因此反向迭代不必要地复杂化。insert()
,使std::copy()
更少需要。Allocator
模板参数,与trivial(QLineEdit
所需的s/QString/secqstring/
相比,使自定义内存管理Qt(需要typedef) })。 编辑20171220 :这样可以减少Qt在C ++ 11和C ++ 17之后的分配器设计进展,参见例如John Lakos' talk(part 2)。std::deque
。std::list
有splice()
。每当我发现自己使用std::list
时,都是因为我需要splice()
。 std::stack
,std::queue
正确聚合其基础容器,并且不会继承它,如QStack
,QQueue
那样。QSet
与std::unordered_set
类似,与std::set
不同。QList
是just weird。上面的许多内容都可能是solved quite easily in 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 :(得分:26)
让我们将这些说法分解为实际可衡量的现象:
在这种情况下提出的主张是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 Style
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;
}
因此,正如我们所看到的,除了一个额外的界面之外,这个界面除了一个已经时尚,流线型和现代化的界面之外什么都没有获得。在已经稳定可用的界面上添加不必要的抽象级别?不是我的想法&#34;更容易&#34;。
此外,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样式迭代器很容易实现这一点,这要归功于它严重依赖于隐式共享,这是一个反文档,如文档here所述,以及许多其他areas。这个&#34;优化&#34;似乎特别奇怪。在越来越多的多线程框架中投入使用,但这是为你做的营销。
这个有点棘手。使用写时复制和隐式共享和增长策略使得实际保证容器在任何给定时间将使用多少内存非常困难。这与STL不同,后者为您提供强大的算法保证。
我们知道the minimal bound of wasted space for a vector is the square root of the length of the vector,但似乎没有办法在Qt中实现这一点;各种&#34;优化&#34;他们支持将排除这个非常重要的节省空间的功能。 STL不需要此功能(并且大多数使用增长倍增,这更浪费),但重要的是要注意,如果需要,您至少可以实现此功能。
双链表也是如此,它可以使用XOr链接来大幅减少使用的空间。同样,由于Qt对增长和COW的要求,这对Qt来说是不可能的。
COW确实可以做得更轻,但是Intrusive Containers也是如此,例如由boost支持,而Qt在早期版本中经常使用这些容器,但它们不再使用,因为它们很难使用,不安全,并给程序员带来负担。 COW是一种不那么具有侵入性的解决方案,但由于上述原因而没有吸引力。没有理由不能使用具有相同内存成本或低于Qt容器的STL容器,并且实际上知道在任何给定时间浪费了多少内存。遗憾的是,不可能在原始内存使用情况中比较两者,因为这些基准测试在不同的用例中会显示出截然不同的结果,这是STL旨在纠正的确切问题。
尽可能避免使用Qt容器而不增加复制成本,并尽可能使用STL类型迭代(可能通过包装器或新语法)。
答案 4 :(得分:22)
STL容器:
答案 5 :(得分:15)
Qt容器使用写时复制习语。
答案 6 :(得分:9)
其中一个主要问题是Qt的API要求你在Qt的容器中提供数据,所以你也可以简单地使用Qt容器而不是在两者之间来回转换。
此外,如果您已经在使用Qt容器,那么单独使用它们可能会稍微优化一些,因为您不必包含STL头文件并可能链接到STL库中。但是,根据您的工具链,无论如何都可能发生这种情况。纯粹从设计的角度来看,一致性通常是件好事。
答案 7 :(得分:8)
如果您使用的数据主要用于驱动基于Qt的UI,那么一定要使用Qt容器。
如果数据主要在应用程序内部使用,并且您永远不可能远离Qt,那么除非性能问题,请使用Qt容器,因为它会使进入UI的数据位更容易处理。
如果数据主要与仅了解STL容器的其他库一起使用,则使用STL容器。如果您遇到这种情况,无论如何都会遇到麻烦,因为无论您做什么,您都会在容器类型之间来回移植。
答案 8 :(得分:7)
除了COW差异之外,STL容器在各种平台上得到更广泛的支持。如果你将你的工作限制在“主流”平台上,Qt是足够便携的,但是STL也可以在许多其他更加模糊的平台上使用(例如,德州仪器的DSP)。
因为STL是标准的而不是由单一公司控制,所以一般来说,有更多程序员可以轻松阅读,理解和修改STL代码和更多资源(书籍,在线论坛,会议等)到支持他们这样做,而不是Qt。这并不是说因为这个原因,人们应该回避Qt;就是这样,在所有其他条件相同的情况下,你应该默认为STL,但当然所有事情都很少相等,所以你必须在你自己的语境中决定最有意义的。
关于AlexKR的答案:STL性能在极限范围内得到保证,但是给定的实现可以利用与平台相关的细节来加速他们的STL。所以从这个意义上说,你可能会在不同的平台上得到不同的结果,但它永远不会比明确的保证(模数错误)慢。
答案 9 :(得分:3)
我的五美分: Qt容器应该在不同平台上工作类似。 而STL容器依赖于STL实现。 您可能会得到不同的性能结果。
修改强>
我并不是说STL“慢”但我指的是效果
各种实施细节。
请检查this,然后查看this。
这不是STL的真正问题。很明显,如果你在性能上有显着差异,那么使用STL的代码就会出现问题。
答案 10 :(得分:3)
我想这取决于你使用Qt的方式。如果您在产品上使用它,那么使用Qt容器可能是有意义的。如果仅包含(例如)UI部分,则最好使用C ++标准容器。
答案 11 :(得分:3)
我认为STL是一个优秀的软件,但如果我要做一些KDE或Qt相关的编程,那么Qt就是要走的路。它也取决于你使用的编译器,GCC STL工作得很好但是如果你不得不使用说SUN Studio CC那么STL很可能会让你头疼,因为编译器不是STL本身。在这种情况下,因为编译器会让你头疼,只需使用Qt来省去麻烦。只是我的2美分......
答案 12 :(得分:3)
QVector有(有时)很大的限制。 It can only allocate int bytes of memory(请注意,限制以字节为单位,而不是元素数)。这意味着尝试使用QVector分配大于〜2GB的连续内存块将导致崩溃。这发生在Qt 4和5中.std :: vector没有这样的限制。
答案 13 :(得分:0)
与我一起使用STL容器的主要原因是,如果您需要自定义分配器,以便在非常大的容器中重用内存。例如,假设您有一个存储1000000个条目(键/值对)的QMap。在Qt中,无论如何都意味着恰好有1亿次分配(new
次呼叫)。在STL中,您始终可以创建一个自定义分配器,在内部一次性分配所有内存,并在填充映射时将其分配给每个条目。
我的建议是在业务逻辑中编写性能关键算法时使用STL容器,然后在结果准备就绪时将它们转换回Qt容器,如果需要,可以通过UI控件和表单显示。