web-dev-qa-db-fra.com

Conteneurs STL ou Qt?

Quels sont les avantages et les inconvénients de l'utilisation des conteneurs Qt (QMap, QVector, etc.) par rapport à leur équivalent STL?

Je peux voir une raison de préférer Qt:

  • Les conteneurs Qt peuvent être transmis à d'autres parties de Qt. Par exemple, ils peuvent être utilisés pour remplir un QVariant puis un QSettings (avec quelques limitations cependant, seulement QList et QMap/QHash dont les clés sont des chaînes sont acceptées).

Y en a-t-il un autre?

Edit: En supposant que l'application repose déjà sur Qt.

178
Julien-L

J'ai commencé par utiliser std::(w)string et les conteneurs STL exclusivement et convertir vers/depuis les équivalents Qt, mais j'ai déjà basculé vers QString et je trouve que j'utilise de plus en plus les conteneurs Qt .

En ce qui concerne les chaînes, QString offre des fonctionnalités beaucoup plus complètes que std::basic_string et il est entièrement compatible avec Unicode. Il offre également une mise en œuvre efficace de COW , sur laquelle je suis devenu très tributaire.

Conteneurs de Qt:

  • offre la même implémentation COW que dans QString, ce qui est extrêmement utile quand il s'agit d'utiliser la macro foreach de Qt (qui fait une copie) et lors de l'utilisation de méta-types ou de signaux et de slots.
  • peut utiliser des itérateurs de style STL ou des itérateurs de style Java
  • sont diffusables avec QDataStream
  • sont largement utilisés dans l'API de Qt
  • avoir une implémentation stable sur tous les systèmes d'exploitation. Une implémentation STL doit obéir à la norme C++, mais est par ailleurs libre de faire comme bon lui semble (voir le std::string Controverse sur la vache). Certaines implémentations STL sont particulièrement mauvaises.
  • fournir des hachages, qui ne sont disponibles que si vous utilisez TR1

Le QTL a une philosophie différente de la STL, qui est bien résumée par J. Blanchette: "Alors que les conteneurs de STL sont optimisés pour la vitesse brute, les classes de conteneurs de Qt ont été soigneusement conçues pour offrir une commodité, une utilisation minimale de la mémoire et une expansion minimale du code. "
Le lien ci-dessus fournit plus de détails sur la mise en œuvre de QTL et les optimisations utilisées.

131
rpg

C'est une question difficile à répondre. Cela peut vraiment se résumer à un argument philosophique/subjectif.

Cela étant dit...

Je recommande la règle "Quand à Rome ... faites comme les Romains"

Ce qui signifie que si vous êtes en terre Qt, codez comme le font les Qt'ians. Ce n'est pas seulement pour des problèmes de lisibilité/cohérence. Considérez ce qui se passe si vous stockez tout dans un conteneur stl, vous devez ensuite transmettre toutes ces données à une fonction Qt. Voulez-vous vraiment gérer un tas de code qui copie des choses dans/hors des conteneurs Qt. Votre code est déjà fortement dépendant de Qt, donc ce n'est pas comme si vous le rendiez plus "standard" en utilisant des conteneurs stl. Et quel est l'intérêt d'un conteneur si chaque fois que vous souhaitez l'utiliser pour quelque chose d'utile, vous devez le copier dans le conteneur Qt correspondant?

173
Doug T.

Les conteneurs Qt sont plus limités que ceux STL. Quelques exemples où les STL sont supérieurs (tous ceux que j'ai rencontrés dans le passé):

  • STL est standardisé, ne change pas avec chaque version de Qt (Qt 2 avait QList (basé sur un pointeur) et QValueList (valeur -based); Qt 3 avait QPtrList et QValueList; Qt 4 a maintenant QList, et ce n'est rien du tout comme QPtrList ouQValueList).
    Même si vous finissez par utiliser les conteneurs Qt, utilisez le sous-ensemble d'API compatible STL (c.-à-d. Push_back(), pas append(); front(), pas first(), ...) pour éviter le portage encore une fois vient Qt 5. Dans les deux transitions Qt2-> 3 et Qt3-> 4, les changements dans les conteneurs Qt étaient parmi ceux nécessitant le plus de churn de code.
  • Les conteneurs bidirectionnels STL ont tous rbegin()/rend(), ce qui rend l'itération inverse symétrique pour l'itération avant. Tous les conteneurs Qt n'en ont pas (les associatifs non), donc l'itération inverse est inutilement compliquée.
  • Les conteneurs STL ont la plage -insert() de différents types d'itérateurs, mais compatibles, ce qui rend std::copy() beaucoup moins souvent nécessaire.
  • Les conteneurs STL ont un argument de modèle Allocator, ce qui rend la gestion de la mémoire personnalisée trivial (typedef requis), par rapport à Qt (fork de QLineEdit requis) pour s/QString/secqstring/). EDIT 20171220 : Cela coupe Qt des avancées dans la conception d'allocateurs suivant C++ 11 et C++ 17, cf. par exemple. conférence de John Lakos ( partie 2 ).
  • Il n'y a pas d'équivalent Qt à std::deque.
  • std::list A splice(). Chaque fois que je me retrouve à utiliser std::list, C'est parce que j'ai besoin de splice().
  • std::stack, std::queue Agrège correctement leur conteneur sous-jacent, et n'en hérite pas, comme QStack , QQueue faire.
  • QSet est comme std::unordered_set, pas comme std::set.
  • QList est un juste bizarre .

Beaucoup de ce qui précède pourrait être résolu assez facilement dans Qt , mais la bibliothèque de conteneurs dans Qt semble connaître un manque de développement pour le moment.

EDIT 20150106: Après avoir passé un certain temps à essayer d'apporter le support C++ 11 aux classes de conteneurs Qt 5, J'ai décidé que ça ne valait pas le coup. Si vous regardez le travail qui est mis dans les implémentations de bibliothèque standard C++, il est clair que les classes Qt ne rattraperont jamais. Nous avons publié Qt 5.4 maintenant et QVector encore ne déplace pas les éléments lors des réallocations, n'a pas emplace_back() ou rvalue -Push_back()... Nous avons également récemment rejeté un modèle de classe QOptional, en attendant std::optional. De même pour std::unique_ptr. J'espère que cette tendance se poursuivra.

62

Décomposons ces affirmations en phénomènes réels mesurables:

  • Plus léger: les conteneurs Qt utilisent moins de mémoire que les conteneurs STL
  • Plus sûr: les conteneurs Qt ont moins de chances d'être mal utilisés
  • Plus facile: les conteneurs Qt présentent moins de fardeau intellectuel

Plus facile

L'affirmation faite dans ce contexte est que l'itération de style Java est en quelque sorte "plus facile" que le style STL, et donc Qt est plus facile à utiliser en raison de cette interface supplémentaire.

Style Java:

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

Style STL:

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

Le style d'itérateur Java a l'avantage d'être un peu plus petit et plus propre. Le problème est que ce n'est plus du style STL.

Style STL C++ 11

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

ou

C++ 11 pour chaque style

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

Ce qui est tellement simple qu'il n'y a aucune raison de ne jamais utiliser autre chose (à moins que vous ne preniez pas en charge C++ 11).

Mon préféré, cependant, est:

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

Donc, comme nous pouvons le voir, cette interface ne nous rapporte rien d'autre qu'une interface supplémentaire, en plus d'une interface déjà élégante, rationalisée et moderne. Ajout d'un niveau d'abstraction inutile au-dessus d'une interface déjà stable et utilisable? Pas mon idée de "plus facile".

En outre, Qt foreach et Java ajoutent une surcharge; elles copient la structure et fournissent un niveau d'indirection inutile. Cela peut ne pas sembler beaucoup, mais pourquoi ajouter une couche de surcharge pour fournir une non -que cette interface est beaucoup plus simple? Java a cette interface parce que Java n'a pas de surcharge d'opérateur; C++ en a.).

Plus sûr

La justification que Qt donne est le problème de partage implicite, qui n'est ni implicite ni un problème. Cela implique cependant le partage.

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

Tout d'abord, ce n'est pas implicite; vous affectez explicitement un vecteur à un autre. La spécification d'itérateur STL indique clairement que les itérateurs appartiennent au conteneur, nous avons donc clairement introduit un conteneur partagé entre b et a. Deuxièmement, ce n'est pas un problème; tant que toutes les règles de la spécification de l'itérateur sont respectées, absolument rien ne se passera mal. La seule fois où quelque chose ne va pas, c'est ici:

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

Qt spécifie cela comme s'il signifiait quelque chose, comme un problème surgit de novo de ce scénario. Ce n'est pas le cas. L'itérateur est invalidé, et comme tout ce qui peut être accessible à partir de plusieurs zones disjointes, c'est comme ça qu'il fonctionne. En fait, cela se produira facilement avec les itérateurs de style Java dans Qt, grâce à sa forte dépendance au partage implicite, qui est un contre-modèle tel que documenté ici , et à bien d'autres autre domaines . Il semble particulièrement étrange que cette "optimisation" soit mise en œuvre dans un framework évoluant de plus en plus vers le multithreading, mais c'est du marketing pour vous.

Plus léger

Celui-ci est un peu plus délicat. L'utilisation de Copy-On-Write et de stratégies de partage et de croissance implicites rend très difficile de garantir réellement la quantité de mémoire que votre conteneur utilisera à un moment donné. C'est différent de la STL, qui vous donne de fortes garanties algorithmiques.

Nous savons la limite minimale de l'espace perdu pour un vecteur est la racine carrée de la longueur du vecteur , mais il semble qu'il n'y ait aucun moyen de l'implémenter dans Qt; les diverses "optimisations" qu'elles prennent en charge empêcheraient cette fonctionnalité très importante d'économie d'espace. La STL ne nécessite pas cette fonctionnalité (et la plupart utilisent une croissance doublée, ce qui est plus inutile), mais il est important de noter que vous pouvez au moins implémenter cette fonctionnalité, si nécessaire.

Il en va de même pour les listes doublement liées, qui pourraient utiliser la liaison XOr pour réduire considérablement l'espace utilisé. Encore une fois, cela est impossible avec Qt, en raison de ses exigences de croissance et de vache.

COW peut en effet rendre quelque chose de plus léger, mais il en va de même pour les conteneurs intrusifs, tels que ceux pris en charge par boost , et Qt les a fréquemment utilisés dans les versions antérieures, mais ils ne sont plus autant utilisés car ils sont difficiles à utiliser. , dangereux et imposer une charge au programmeur. COW est une solution beaucoup moins intrusive, mais peu attrayante pour les raisons exposées ci-dessus.

Il n'y a aucune raison pour laquelle vous ne pourriez pas utiliser des conteneurs STL avec le même coût de mémoire ou moins que les conteneurs de Qt, avec l'avantage supplémentaire de savoir réellement combien de mémoire vous gaspillerez à un moment donné. Il est malheureusement impossible de comparer les deux dans l'utilisation de la mémoire brute, car de tels tests de référence afficheraient des résultats extrêmement différents dans différents cas d'utilisation, ce qui est le type exact de problème que la STL a été conçue pour corriger.

En conclusion

Évitez d'utiliser des conteneurs Qt lorsque cela est possible sans imposer de coût de copie et utilisez l'itération de type STL (peut-être via un wrapper ou la nouvelle syntaxe), dans la mesure du possible.

28
Alice

Conteneurs STL:

  • Ont des garanties de performance
  • Peut être utilisé dans les algorithmes STL qui ont également des garanties de performances
  • Peut être exploité par des bibliothèques C++ tierces comme Boost
  • Sont des solutions propriétaires standard et susceptibles de survivre
  • Encourager la programmation générique d'algorithmes et de structures de données. Si vous écrivez de nouveaux algorithmes et structures de données conformes à STL, vous pouvez tirer parti de ce que STL fournit déjà sans frais.
23
fbrereto

Les conteneurs Qt utilisent un idiome de copie sur écriture.

15
TimW

L'un des principaux problèmes est que l'API de Qt s'attend à ce que vous fournissiez des données dans les conteneurs de Qt, vous pouvez donc aussi simplement utiliser les conteneurs Qt plutôt que de faire des allers-retours entre les deux.

De plus, si vous utilisez déjà les conteneurs Qt, il pourrait être légèrement plus optimal de les utiliser exclusivement, car vous n'auriez pas à inclure les fichiers d'en-tête STL et éventuellement à lier dans les bibliothèques STL. Cependant, selon votre chaîne d'outils, cela peut arriver de toute façon. Du point de vue de la conception, la cohérence est généralement une bonne chose.

9
qid

Si les données avec lesquelles vous travaillez sont principalement utilisées pour piloter l'interface utilisateur basée sur Qt, utilisez certainement des conteneurs Qt.

Si les données sont principalement utilisées en interne dans l'application, et que vous ne risquez jamais de vous éloigner de Qt, à moins de problèmes de performances, utilisez les conteneurs Qt car cela rendra plus faciles à gérer les bits de données qui vont à l'interface utilisateur.

Si les données sont principalement utilisées conjointement avec d'autres bibliothèques qui ne connaissent que les conteneurs STL, utilisez des conteneurs STL. Si vous avez cette situation, vous avez des ennuis, peu importe quoi, car vous allez faire beaucoup de portage entre les types de conteneurs, peu importe ce que vous faites.

8
Michael Kohne

Outre la différence COW, les conteneurs STL sont beaucoup plus largement pris en charge sur une variété de plates-formes. Qt est suffisamment portable si vous limitez votre travail aux plates-formes "grand public", mais la STL est également disponible sur de nombreuses autres plates-formes plus obscures (par exemple, les DSP de Texas Instruments).

Parce que la STL est standard plutôt que contrôlée par une seule société, il y a, en général, plus de programmeurs qui peuvent facilement lire, comprendre et modifier le code STL et plus de ressources (livres, forums en ligne, conférences, etc.) pour les soutenir dans faire cela qu'il n'y en a pour Qt. Cela ne veut pas dire que l'on devrait se détourner de Qt pour cette seule raison; juste que, toutes choses étant égales par ailleurs, vous devez utiliser par défaut la STL, mais bien sûr toutes choses sont rarement égales, vous devrez donc décider dans votre propre contexte ce qui a le plus de sens.

En ce qui concerne la réponse d'AlexKR: les performances STL sont garanties dans certaines limites, mais une implémentation donnée peut utiliser des détails dépendants de la plateforme pour accélérer leur STL. Donc, dans ce sens, vous pouvez obtenir des résultats différents sur différentes plates-formes, mais ce ne sera jamais plus lent que la garantie explicite (bogues modulo).

7
metal

Je suppose que cela dépend de la façon dont vous utilisez Qt. Si vous l'utilisez partout sur votre produit, il est probablement judicieux d'utiliser des conteneurs Qt. Si vous le contenez uniquement (par exemple) à la partie d'interface utilisateur, il peut être préférable d'utiliser des conteneurs standard C++.

3

Je suis d'avis que STL est un excellent logiciel, mais si je dois faire de la programmation liée à KDE ou Qt, alors Qt est le chemin à parcourir. Cela dépend également du compilateur que vous utilisez, avec GCC STL fonctionne assez bien, mais si vous devez utiliser, disons Sun Studio CC, STL vous apportera probablement des maux de tête à cause du compilateur et non de la STL en soi. Dans ce cas, car le compilateur vous fera mal à la tête, utilisez simplement Qt pour vous éviter les ennuis. Juste mes 2 cents ...

3
Paulo Lopes

Mes cinq cents: les conteneurs Qt sont censés fonctionner de manière similaire sur différentes plates-formes. Alors que les conteneurs STL dépendent de l'implémentation STL. Vous pouvez obtenir des résultats de performances différents.

EDIT: Je ne dis pas que STL est "plus lent" mais je souligne les effets de divers détails d'implémentation.
Veuillez cocher this , puis peut-être this .
Et ce n'est pas un vrai problème de STL. De toute évidence, si vous avez une différence significative de performances, il y a un problème dans le code qui utilise STL.

3
alexkr

Il y a (parfois) une grande limitation dans QVector. Il ne peut allouer que des octets int de mémoire (notez que la limite est en octets et non en nombre d'éléments). Cela implique qu'essayer d'allouer des blocs de mémoire contigus supérieurs à ~ 2 Go avec un QVector entraînera un plantage. Cela se produit avec Qt 4 et 5. std :: vector n'a pas une telle limitation.

3
fedemp

La principale raison pour laquelle j'utilise des conteneurs STL est si vous avez besoin d'un allocateur personnalisé afin de réutiliser la mémoire dans de très gros conteneurs. Supposons par exemple que vous ayez un QMap qui stocke 1000000 entrées (paires clé/valeur). Dans Qt, cela implique exactement 1000000 millions d'allocations (appels new) quoi qu'il arrive. Dans STL, vous pouvez toujours créer un allocateur personnalisé qui alloue en interne toute cette mémoire à la fois et l'affecter à chaque entrée lorsque la carte est remplie.

Mon conseil est d'utiliser des conteneurs STL lors de l'écriture d'algorithmes critiques de performances dans la logique métier, puis de les reconvertir en conteneurs Qt lorsque les résultats sont prêts à être affichés par vos contrôles et formulaires d'interface utilisateur si nécessaire.

0
Darien Pardinas