Answers:
我开始只使用std::(w)string
STL容器并与Qt等效项进行相互转换,但是我已经切换到QString
Qt的容器,并且发现我越来越多地使用Qt的容器。
在字符串方面,与之QString
相比std::basic_string
,它提供了更完善的功能,并且完全支持Unicode。它还提供了有效的COW实施,我已经高度依赖它了。
Qt的容器:
QString
,这在使用Qt的foreach
宏(执行复制操作)以及使用元类型或信号和插槽时非常有用。QDataStream
std::string
COW争议)。一些STL实现特别糟糕。QTL与STL有不同的理念,J。Blanchette对此进行了很好的总结:“虽然STL的容器针对原始速度进行了优化,但是Qt的容器类经过精心设计,以提供便利,最小的内存使用和最小的代码扩展。”
上面的链接提供了有关QTL实施以及使用了哪些优化的更多详细信息。
QList<double>
32位体系结构的概要文件,供内存使用,供您自己查看。
QVector
而不是QList
。Qt有一个很好的解释,即QList旨在将指针存储在对象上。因此,动态创建的每个双项和指向该项的指针都存储到中QList
。QList被设计为向量和链接列表之间的“中间”容器。它不是设计用于内存/性能至关重要的情况。
这是一个很难回答的问题。它确实可以归结为哲学/主观论点。
话虽如此...
我建议规则“在罗马时...像罗马人一样做”
这意味着如果您在Qt国度,请像Qt'ians一样进行编码。这不仅是出于可读性/一致性方面的考虑。考虑一下如果将所有内容存储在stl容器中会发生什么,那么必须将所有数据传递给Qt函数。您是否真的要管理一堆将事物复制到Qt容器中/从其中复制出来的代码。您的代码已经非常依赖Qt了,因此它并不像您通过使用stl容器使其变得更“标准”一样。容器的意义何在?如果您每次想将其用于有用的东西时,都必须将其复制到相应的Qt容器中?
Qt容器比STL容器更受限制。STL优越的几个例子(我过去曾提到过的所有例子):
QList
(指针型)和QValueList
(价值为基础); Qt的3例QPtrList
和QValueList
; Qt的4现在有QList
,而且它的什么都没有像QPtrList
或 QValueList
)。push_back()
,不要append()
; front()
,first()
也要,...),以避免再次移植Qt5。在Qt2-> 3和Qt3-> 4中转换中,Qt容器中的更改属于需要代码量最大的更改。rbegin()
/ rend()
,使得反向迭代对称于正向迭代。并非所有Qt容器都具有它们(关联的容器没有),因此反向迭代不必要地复杂。insert()
不同的但兼容的迭代器类型,从而std::copy()
减少了使用的频率。Allocator
模板参数,使得自定义的内存管理微不足道(typedef的要求),比的Qt(叉QLineEdit
需要s/QString/secqstring/
)。EDIT 20171220:这切断了Qt,使之不再遵循C ++ 11和C ++ 17之后的分配器设计的进步,请参见。例如John Lakos的演讲(第2部分)。std::deque
。std::list
有splice()
。每当我发现自己使用时std::list
,是因为我需要splice()
。 std::stack
,std::queue
正确汇集其基本容器,不继承它,因为QStack
,QQueue
这样做。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
。我希望这种趋势继续下去。
QList
是相当于std::deque
。显然,我不应该只浏览文档。
QVector
不得不crbegin
因为Qt的5.6和朋友
std::reverse_iterator
损坏的QHash
/ QMap
迭代器,当它被取消引用时,返回mapped_type
而不是value_type
)。没有什么不能解决的,但是请看我从2015
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) )。
让我们将这些主张分解为实际的可衡量现象:
在这种情况下,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类型迭代(可能通过包装器或新语法)。
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之前的兼容编译器一起使用。
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并非如此。我确定这不是您的意思,但这就是它的实现方式。因此是“误导性信息”。
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?上述所有的?
STL容器:
Qt容器使用写时复制习惯。
std::basic_string
,该标准对C ++ 11采取了措施以使其与标准不符。
主要问题之一是Qt的API希望您在Qt的容器中提供数据,因此您也可以简单地使用Qt容器而不是在两者之间来回转换。
另外,如果您已经在使用Qt容器,则最好单独使用它们,因为您不必包括STL头文件,也不必在STL库中链接。但是,根据您的工具链,还是可能会发生这种情况。纯粹从设计角度来看,一致性通常是一件好事。
除了COW的不同之外,STL容器在各种平台上得到了更广泛的支持。如果您将工作限制在“主流”平台上,则Qt具有足够的可移植性,但是STL也可以在许多其他晦涩的平台(例如,德州仪器(TI)的DSP)上使用。
因为STL是标准的,而不是由一个公司控制的,所以通常来说,有更多的程序员可以轻松阅读,理解和修改STL代码以及更多的资源(书籍,在线论坛,会议等)来支持它们。这样做比Qt的要多。这并不是说仅出于这个原因就应该回避Qt。只是,在所有其他条件都相同的情况下,您应该默认使用STL,但是当然所有条件很少相等,因此您必须在自己的上下文中做出决定,这才是最有意义的。
关于AlexKR的答案:STL性能在一定范围内得到保证,但是给定的实现可能利用平台相关的细节来加快其STL。因此,从这个意义上讲,您可能在不同的平台上获得不同的结果,但是它永远不会比显式保证(模块错误)慢。
我的5分钱:Qt容器在不同平台上的工作原理类似。而STL容器取决于STL的实现。您可能会获得不同的性能结果。
编辑:
我不是在告诉STL是“慢”,但我指出各种实现细节的影响。
请检查这个,然后也许这个。
这不是STL的真正问题。显然,如果您在性能上有显着差异,则使用STL的代码中存在问题。
我想这取决于您使用Qt的方式。如果您在产品上全部使用它,那么使用Qt容器可能更有意义。如果仅将其包含在(例如)UI部分中,则最好使用C ++标准容器。
QVector中(有时)有一个很大的限制。它只能分配int字节的内存(请注意,限制以字节为单位,而不是元素数)。这意味着尝试使用QVector分配大于2GB的连续内存块将导致崩溃。Qt 4和5会发生这种情况。std :: vector没有这种限制。
对我来说,使用STL容器的主要原因是,如果您需要自定义分配器以便在非常大的容器中重用内存。例如,假设您有一个存储1000000个条目(键/值对)的QMap。在Qt中,new
无论如何,这恰好意味着1万亿个分配(调用)。在STL中,您始终可以创建一个自定义分配器,该分配器在内部一次分配所有内存,并在填充映射时将其分配给每个条目。
我的建议是在业务逻辑中编写性能关键算法时使用STL容器,然后在准备好结果时将其转换回Qt容器,并由UI控件和表单显示(如果需要)。
QMapNode<K,V>
为你K
,V
提供你自己的operator new
。