wake-up-neo.com

STL- oder Qt-Container?

Was sind die Vor- und Nachteile der Verwendung von Qt-Containern (QMap, QVector usw.) gegenüber ihrer STL-Entsprechung?

Ich sehe einen Grund, Qt vorzuziehen:

  • Qt-Container können an andere Teile von Qt weitergereicht werden. Zum Beispiel können sie verwendet werden, um ein QVariant und dann ein QSettings zu füllen (mit einer gewissen Einschränkung jedoch nur QList und QMap/QHash, dessen Schlüssel Zeichenfolgen sind, werden akzeptiert).

Gibt es noch andere?

Bearbeiten: Angenommen, die Anwendung basiert bereits auf Qt.

178
Julien-L

Ich habe zunächst ausschließlich std::(w)string und die STL-Container verwendet und in/aus den Qt-Entsprechungen konvertiert, bin aber bereits auf QString umgestiegen und stelle fest, dass ich immer mehr Qt-Container verwende .

Wenn es um Zeichenfolgen geht, bietet QString eine viel umfassendere Funktionalität als std::basic_string und es ist vollständig Unicode-fähig. Es bietet auch eine effiziente COW-Implementierung , auf die ich mich sehr verlassen habe.

Qt's Behälter:

  • bieten die gleiche COW-Implementierung wie in QString, was äußerst nützlich ist, wenn Sie das Makro foreach von Qt verwenden (das kopiert) und wenn Sie Metatypen oder Signale und Slots verwenden.
  • kann Iteratoren im STL-Stil oder im Java-Stil verwenden
  • sind streambar mit QDataStream
  • werden häufig in der API von Qt verwendet
  • betriebssystemübergreifend eine stabile Implementierung haben. Eine STL-Implementierung muss dem C++ - Standard entsprechen, kann aber ansonsten nach Belieben ausgeführt werden (siehe std::string COW Kontroverse). Einige STL-Implementierungen sind besonders schlecht.
  • geben Sie Hashes an, die nur verfügbar sind, wenn Sie TR1 verwenden

Die QTL hat eine andere Philosophie als die STL, die J. Blanchette gut zusammenfasst : "Während die Container von STL für die Rohgeschwindigkeit optimiert sind, sind die Containerklassen von Qt wurden sorgfältig entwickelt, um Komfort, minimale Speichernutzung und minimale Codeerweiterung zu gewährleisten. "
Über den obigen Link erfahren Sie mehr über die Implementierung der QTL und welche Optimierungen verwendet werden.

131
rpg

Dies ist eine schwer zu beantwortende Frage. Es kann sich wirklich auf ein philosophisches/subjektives Argument beschränken.

Davon abgesehen ...

Ich empfehle die Regel "When in Rome ... Do as the Romans Do"

Das heißt, wenn Sie sich in Qt Land befinden, codieren Sie wie die Qt'ians. Dies ist nicht nur aus Gründen der Lesbarkeit/Konsistenz wichtig. Überlegen Sie, was passiert, wenn Sie alles in einem stl-Container speichern und dann alle diese Daten an eine Qt-Funktion übergeben müssen. Wollen Sie wirklich eine Menge Code verwalten, der Dinge in/aus Qt-Containern kopiert? Ihr Code ist bereits stark von Qt abhängig, so dass es nicht so ist, als würden Sie ihn durch die Verwendung von STL-Containern zu einem "Standard" machen. Und was nützt ein Container, wenn Sie ihn jedes Mal für irgendetwas Nützliches verwenden möchten, müssen Sie ihn in den entsprechenden Qt-Container kopieren?

173
Doug T.

Die Qt-Container sind eingeschränkter als die STL-Container. Ein paar Beispiele, wo die STL überlegen sind (all dies habe ich in der Vergangenheit getroffen):

  • STL ist standardisiert, ändert sich nicht mit jeder Qt-Version (Qt 2 hatte QList (Zeiger-basiert) und QValueList (Wert) -basiert); Qt 3 hatte QPtrList und QValueList; Qt 4 hat jetzt QList und es ist überhaupt nichts wie QPtrList oderQValueList).
    Verwenden Sie die STL-kompatible API-Teilmenge (dh Push_back(), nicht append(); front(), auch wenn Sie letztendlich die Qt-Container verwenden. nicht first(), ...), um die Portierung noch einmal zu vermeiden, kommt Qt 5. In beiden Übergängen Qt2-> 3 und Qt3-> 4 gehörten die Änderungen in den Qt-Containern zu denjenigen, die die meiste Codeabwanderung erfordern.
  • Alle bidirektionalen AWL-Container haben rbegin()/rend(), wodurch die umgekehrte Iteration symmetrisch zur vorwärts gerichteten Iteration wird. Nicht alle Qt-Container haben sie (die assoziativen nicht), daher ist die umgekehrte Iteration unnötig kompliziert.
  • AWL-Container haben den Bereich -insert() von verschiedenen, aber kompatiblen Iteratortypen, wodurch std::copy() viel seltener benötigt wird.
  • STL-Container verfügen über ein Allocator -Vorlagenargument, wodurch eine benutzerdefinierte Speicherverwaltung trivial (typedef erforderlich) im Vergleich zu Qt (Fork von QLineEdit erforderlich ist für s/QString/secqstring/). EDIT 20171220 : Hiermit werden die Fortschritte beim Allokator-Design nach C++ 11 und C++ 17 verringert, vgl. z.B. Rede von John Lakos ( Teil 2 ).
  • Es gibt kein Qt-Äquivalent zu std::deque.
  • std::list Hat splice(). Immer wenn ich std::list Verwende, brauche ich splice().
  • std::stack, std::queue Aggregieren ihren zugrunde liegenden Container ordnungsgemäß und erben ihn nicht als QStack , QQueue do.
  • QSet ist wie std::unordered_set, nicht wie std::set.
  • QList ist ein nur seltsam .

Viele der oben genannten Probleme könnten in Qt recht einfach gelöst sein, aber in der Container-Bibliothek in Qt scheint es derzeit an Entwicklungsfokus zu mangeln.

EDIT 20150106: Nachdem Sie einige Zeit damit verbracht haben, C++ 11-Unterstützung für Qt 5-Containerklassen bereitzustellen, Ich habe entschieden, dass es die Arbeit nicht wert ist. Wenn Sie sich die Arbeit ansehen, die in C++ - Standardbibliotheksimplementierungen implementiert wird, ist es ziemlich klar, dass die Qt-Klassen niemals aufholen werden. Wir haben jetzt Qt 5.4 veröffentlicht und QVector still verschiebt keine Elemente auf Neuzuordnungen, hat keine emplace_back() oder rvalue -Push_back()... Wir haben kürzlich auch eine QOptional Klassenvorlage abgelehnt und stattdessen auf std::optional Gewartet. Ebenso für std::unique_ptr. Ich hoffe, dass sich dieser Trend fortsetzt.

62

Teilen wir diese Behauptungen in tatsächlich messbare Phänomene auf:

  • Leichter: Qt-Container belegen weniger Speicher als STL-Container
  • Sicherer: Qt-Behälter haben weniger Möglichkeiten, unsachgemäß verwendet zu werden
  • Einfacher: Qt-Container stellen eine geringere intellektuelle Belastung dar

Einfacher

In diesem Zusammenhang wird behauptet, dass die Iteration im Java-Stil "einfacher" als im STL-Stil ist und Qt daher aufgrund dieser zusätzlichen Schnittstelle einfacher zu verwenden ist.

Java Style:

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

STL-Stil:

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

Der Iteratorstil Java hat den Vorteil, dass er etwas kleiner und übersichtlicher ist. Das Problem ist, dass dies kein STL-Stil mehr ist.

C++ 11 STL Style

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

oder

C++ 11 für jeden Stil

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

Das ist so drastisch einfach, dass es keinen Grund gibt, jemals etwas anderes zu verwenden (es sei denn, Sie unterstützen C++ 11 nicht).

Mein Favorit ist jedoch:

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

Wie wir sehen können, bringt uns diese Benutzeroberfläche nichts als eine zusätzliche Benutzeroberfläche, zusätzlich zu einer bereits schlanken, optimierten und modernen Benutzeroberfläche. Hinzufügen einer unnötigen Abstraktionsebene zu einer bereits stabilen und verwendbaren Schnittstelle? Nicht meine Vorstellung von "einfacher".

Außerdem fügen Qt foreach und Java - Schnittstellen Overhead hinzu; sie kopieren die Struktur und bieten eine unnötige Indirektionsebene. Dies scheint nicht viel zu sein, aber warum fügen Sie eine Schicht von Overhead hinzu, um einen Nicht-Overhead bereitzustellen? -das-viel-einfachere Interface? Java hat dieses Interface, weil Java keine Operatorüberladung hat; C++ hat.

Sicherer

Die Rechtfertigung, die Qt gibt, ist das implizite Teilungsproblem, das weder implizit noch problematisch ist. Es beinhaltet jedoch das Teilen.

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.
*/

Erstens ist dies nicht implizit; Sie weisen explizit einen Vektor einem anderen zu. Die STL-Iteratorspezifikation gibt eindeutig an, dass Iteratoren zum Container gehören. Daher haben wir einen gemeinsam genutzten Container zwischen b und a eingeführt. Zweitens ist dies kein Problem. Solange alle Regeln der Iteratorspezifikation befolgt werden, wird absolut nichts schief gehen. Das einzige Mal, wenn etwas schief geht, ist hier:

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

Qt gibt dies so an, als würde es etwas bedeuten, als würde aus diesem Szenario de novo ein Problem entstehen. Das tut es nicht. Der Iterator ist ungültig, und genau wie alles, auf das von mehreren nicht zusammenhängenden Bereichen aus zugegriffen werden kann, funktioniert es auch so. Tatsächlich wird dies bei Java -Stiliteratoren in Qt leicht vorkommen, da es stark auf implizites Teilen angewiesen ist. Dies ist ein Gegenmuster, wie dokumentiert hier und bei vielen other areas . Es scheint besonders seltsam, diese "Optimierung" in einem Framework einzusetzen, das sich immer mehr in Richtung Multithreading bewegt, aber das ist Marketing für Sie.

Leichter

Dieser ist etwas kniffliger. Die Verwendung von Copy-On-Write- und impliziten Sharing- und Wachstumsstrategien macht es sehr schwierig, tatsächlich Garantien darüber zu geben, wie viel Speicher Ihr Container zu einem bestimmten Zeitpunkt verwenden wird. Dies unterscheidet sich von der STL, die Ihnen starke algorithmische Garantien bietet.

Wir wissen, dass die minimale Grenze von verschwendetem Platz für einen Vektor die Quadratwurzel der Länge des Vektors ist , aber es scheint keine Möglichkeit zu geben, dies in Qt zu implementieren; Die verschiedenen "Optimierungen", die sie unterstützen, würden diese sehr wichtige platzsparende Funktion ausschließen. Für die STL ist diese Funktion nicht erforderlich (und die meisten verwenden ein doppeltes Wachstum, was verschwenderischer ist). Es ist jedoch wichtig zu beachten, dass Sie diese Funktion bei Bedarf zumindest implementieren können.

Gleiches gilt für doppelt verknüpfte Listen, bei denen mithilfe von XOr-Verknüpfungen der verwendete Speicherplatz drastisch reduziert werden kann. Auch dies ist bei Qt aufgrund der Anforderungen an Wachstum und COW nicht möglich.

COW kann zwar etwas leichter machen, aber auch Intrusive Container, wie sie von boost unterstützt werden, und Qt, die in früheren Versionen häufig verwendet wurden, werden nicht mehr so ​​häufig verwendet, da sie schwer zu verwenden sind , unsicher und belasten den Programmierer. COW ist eine viel weniger aufdringliche Lösung, aber aus den oben genannten Gründen unattraktiv.

Es gibt keinen Grund, warum Sie STL-Container nicht mit den gleichen Speicherkosten oder weniger als die Container von Qt verwenden könnten, mit dem zusätzlichen Vorteil, tatsächlich zu wissen, wie viel Speicher Sie zu einem bestimmten Zeitpunkt verschwenden werden. Leider ist es nicht möglich, die beiden Werte in Bezug auf die Nutzung des Rohspeichers zu vergleichen, da solche Benchmarks in verschiedenen Anwendungsfällen sehr unterschiedliche Ergebnisse zeigen würden, was genau das Problem ist, für das die STL entwickelt wurde.

Abschließend

Vermeiden Sie die Verwendung von Qt-Containern, wenn dies ohne Kopierkosten möglich ist, und verwenden Sie nach Möglichkeit eine Iteration vom Typ STL (möglicherweise über einen Wrapper oder die neue Syntax).

28
Alice

AWL-Behälter:

  • Leistungsgarantien haben
  • Kann in STL-Algorithmen verwendet werden die auch Leistungsgarantien haben
  • Kann von C++ - Bibliotheken von Drittanbietern wie Boost genutzt werden
  • Sind Standard und werden wahrscheinlich proprietäre Lösungen überleben
  • Förderung der allgemeinen Programmierung von Algorithmen und Datenstrukturen. Wenn Sie neue Algorithmen und Datenstrukturen schreiben, die STL entsprechen, können Sie das, was STL bereits bietet, kostenlos nutzen.
23
fbrereto

Qt-Container verwenden die Copy-on-Write-Sprache.

15
TimW

Eines der Hauptprobleme ist, dass die API von Qt erwartet, dass Sie Daten in den Containern von Qt bereitstellen. Sie können also auch einfach die Qt-Container verwenden, anstatt sie zwischen den beiden hin und her zu transformieren.

Wenn Sie die Qt-Container bereits verwenden, ist es möglicherweise etwas optimaler, sie ausschließlich zu verwenden, da Sie die STL-Headerdateien nicht einschließen und möglicherweise in die STL-Bibliotheken verlinken müssen. Abhängig von Ihrer Toolchain kann dies jedoch trotzdem passieren. Rein aus gestalterischer Sicht ist Konsistenz im Allgemeinen eine gute Sache.

9
qid

Wenn die Daten, mit denen Sie arbeiten, hauptsächlich zur Steuerung der Qt-basierten Benutzeroberfläche verwendet werden, sollten Sie auf jeden Fall Qt-Container verwenden.

Wenn die Daten meistens intern in der App verwendet werden und Sie sich wahrscheinlich nie von Qt entfernen, sollten Sie die Qt-Container verwenden, um Leistungsprobleme auszuschließen, da die Datenbits, die zur Benutzeroberfläche gelangen, einfacher zu verarbeiten sind.

Wenn die Daten hauptsächlich in Verbindung mit anderen Bibliotheken verwendet werden, die nur STL-Container kennen, verwenden Sie STL-Container. Wenn Sie in dieser Situation sind, sind Sie in Schwierigkeiten, egal was passiert, weil Sie viel zwischen Containertypen hin und her portieren werden, egal was Sie tun.

8
Michael Kohne

Abgesehen von den Unterschieden bei der COW werden STL-Container auf einer Vielzahl von Plattformen viel häufiger unterstützt. Qt ist portabel genug, wenn Sie Ihre Arbeit auf "Mainstream" -Plattformen beschränken, aber die STL ist auch auf vielen anderen undurchsichtigen Plattformen verfügbar (z. B. DSPs von Texas Instruments).

Da die STL Standard ist und nicht von einem einzelnen Unternehmen kontrolliert wird, gibt es im Allgemeinen mehr Programmierer, die den STL-Code leicht lesen, verstehen und ändern können, und mehr Ressourcen (Bücher, Online-Foren, Konferenzen usw.), um sie dabei zu unterstützen dies zu tun, als es für Qt gibt. Das soll nicht heißen, dass man allein aus diesem Grund vor Qt zurückschrecken sollte; Abgesehen davon, dass alle anderen Dinge gleich sind, sollten Sie standardmäßig die STL verwenden, aber natürlich sind alle Dinge selten gleich, sodass Sie in Ihrem eigenen Kontext entscheiden müssen, was am sinnvollsten ist.

Zur Antwort von AlexKR: Die STL-Leistung ist in Grenzen garantiert, aber eine bestimmte Implementierung kann plattformabhängige Details verwenden, um ihre STL zu beschleunigen . In diesem Sinne erhalten Sie möglicherweise auf verschiedenen Plattformen unterschiedliche Ergebnisse, diese sind jedoch niemals langsamer als die explizite Garantie (Modulo-Fehler).

7
metal

Ich denke, es hängt davon ab, wie Sie Qt verwenden. Wenn Sie es überall in Ihrem Produkt verwenden, ist es wahrscheinlich sinnvoll, Qt-Behälter zu verwenden. Wenn Sie es nur für (zum Beispiel) den UI-Teil enthalten, ist es möglicherweise besser, C++ - Standardcontainer zu verwenden.

3

Ich bin der Meinung, dass STL eine hervorragende Software ist, aber wenn ich KDE- oder Qt-bezogene Programmierung machen möchte, ist Qt der richtige Weg. Es hängt auch von dem Compiler ab, den Sie verwenden. Mit GCC STL funktioniert es ziemlich gut. Wenn Sie jedoch beispielsweise Sun Studio CC verwenden müssen, wird STL Ihnen höchstwahrscheinlich Kopfschmerzen bereiten, da der Compiler nicht die STL an sich enthält. In diesem Fall, da der Compiler Kopfschmerzen verursacht, können Sie mit Qt die Mühe sparen. Nur meine 2 Cent ...

3
Paulo Lopes

Meine fünf Cent: Qt-Container sollen auf verschiedenen Plattformen ähnlich funktionieren. Während STL-Container von der STL-Implementierung abhängen. Möglicherweise erhalten Sie unterschiedliche Leistungsergebnisse.

EDIT: Ich sage nicht, dass STL "langsamer" ist, aber ich weise auf die Auswirkungen verschiedener Implementierungsdetails hin.
Bitte überprüfen Sie this und dann vielleicht this .
Und es ist kein wirkliches Problem von STL. Wenn Sie einen signifikanten Leistungsunterschied haben, liegt offensichtlich ein Problem im Code vor, der STL verwendet.

3
alexkr

In QVector gibt es (manchmal) große Einschränkungen. Es können nur ganze Bytes Speicher zugewiesen werden (Beachten Sie, dass das Limit in Bytes und nicht in der Anzahl der Elemente liegt). Dies impliziert, dass der Versuch, zusammenhängende Speicherblöcke größer als ~ 2 GB mit einem QVector zuzuweisen, zu einem Absturz führt. Dies ist bei Qt 4 und 5 der Fall. Std :: vector unterliegt keiner solchen Einschränkung.

3
fedemp

Der Hauptgrund, warum ich mich für STL-Container entscheide, ist, wenn Sie einen benutzerdefinierten Allokator benötigen, um Speicher in sehr großen Containern wiederzuverwenden. Angenommen, Sie haben eine QMap, in der 1000000 Einträge (Schlüssel/Wert-Paare) gespeichert sind. In Qt impliziert dies genau 1000000 Millionen Zuweisungen (new Aufrufe), egal was passiert. In STL können Sie jederzeit einen benutzerdefinierten Allokator erstellen, der den gesamten Speicher intern auf einmal zuordnet und jedem Eintrag beim Auffüllen der Zuordnung zuweist.

Mein Rat: Verwenden Sie STL-Container, wenn Sie leistungskritische Algorithmen in die Geschäftslogik schreiben, und konvertieren Sie sie dann zurück in Qt-Container, wenn die Ergebnisse von den Steuerelementen und Formularen der Benutzeroberfläche angezeigt werden können.

0
Darien Pardinas