std::forward_list et std::forward_list::push_back
Je voudrais utiliser std::forward_list
Parce que:
La liste de renvoi est un conteneur qui prend en charge rapide d'insertion et de retrait
des éléments de n'importe où à partir du conteneur
Mais il n'y a pas de *std::forward_list::push_back* la mise en œuvre.
Est il y a une haute performance de façon à ajouter le support de l'une ou aucune raison de le faire?
Pourquoi avez-vous besoin? Voulez-vous développer votre liste de deux façons? Ne pouvez-vous pas utiliser
Notez que la bidirectionnel
Je veux garder le tri de la liste
Si vous souhaitez qu'elles soient triées, peut-être
La liste liée individuellement sera légèrement plus rapide pour les opérations de prise en charge (puisque chaque opération de mise à jour de la moitié du nombre des pointeurs), mais tous les deux ont la même complexité des commandes. Il sera probablement plus lent, si vous voulez soutenir un rapide
push_front()
tout aussi facilement?Notez que la bidirectionnel
std::list
prend également en charge "rapide d'insertion et de suppression d'éléments de n'importe où à partir du conteneur", et a push_back
. Le coût est un pointeur par entrée. La mémoire est si serré que vous ne pouvez pas l'utiliser?Je veux garder le tri de la liste
Si vous souhaitez qu'elles soient triées, peut-être
set
ou multiset
pourrait être un meilleur choix? Qui maintient un ordre de tri, au prix d'un ralentissement de l'insertion et le retrait (bien que l'insertion d'un élément unique sera plus rapide que l'insertion dans une liste de recours).La liste liée individuellement sera légèrement plus rapide pour les opérations de prise en charge (puisque chaque opération de mise à jour de la moitié du nombre des pointeurs), mais tous les deux ont la même complexité des commandes. Il sera probablement plus lent, si vous voulez soutenir un rapide
push_back
.OriginalL'auteur Alexander Abashkin | 2012-01-05
Vous devez vous connecter pour publier un commentaire.
std::forward_list
prend en charge rapide d'insertion et de retrait, mais pas de la traversée à la fin. Pour mettre en œuvre.push_back
, vous aurez d'abord besoin d'obtenir à la fin de la liste, qui est O(N) et n'est pas rapide du tout, ce qui est probablement la raison pour laquelle il n'est pas mis en œuvre.Vous pourriez trouver l'itérateur le dernier élément en incrémentant
.before_begin
N foiset ensuite utiliser
.insert_after
ou.emplace_after
pour insérer l'élément:forward_list
pourrait être modifiée par le stockage supplémentaire itérateur pointant vers sa fin. De cette façon,push_back
pourrait être fait en O(1) fois.Donc, il n'y a pas de raison de la mise en œuvre
push_back
?Je crois que va se transformer en une
std::list
.Il aura quand même pas la bidirectionnalité.
non,
std::list
est doublement lié. Ce que je veux dire, c'est de stocker un pointeur supplémentaire à la fin dans la tête de la structure, exactement ce que Mike Seymour suggère.OriginalL'auteur kennytm
Je recommande contre
std::forward_list
juste comme je le déconseillestd::list
dans presque toutes les situations. Personnellement, je n'ai jamais trouvé une situation dans mon code où une liste chaînée est la meilleure structure de données.En C++, par défaut de votre go-pour la collecte des données devrait être
std::vector
. Il vous donne efficacepush_back
, si c'est ce que vous avez vraiment besoin. Techniquement ne vous donne pas efficace de suppression et d'insertion à partir du milieu si vous regardez seulement abstrait big-O de la complexité des mesures de cette opération. Dans le monde réel, cependant,std::vector
gagne encore même d'insertion et de suppression dans le milieu.Comme un exemple, Bjarne Stroustrup créé un test de 100.000 élément
std::list
vsstd::vector
. Il allait lancer une recherche pour un élément et de le supprimer. Alors qu'il allait trouver un point d'insertion et l'insérer dans le milieu. Il aurait pu utiliser une recherche binaire sur lestd::vector
, mais n'a pas à faire la comparaison "plus juste".Les résultats montrent une importante victoire pour les
std::vector
, même dans cette situation oùstd::list
est censé être forte. Simplement en traversant lestd::list
prend beaucoup plus de temps en raison de la distance qui les sépare dans la mémoire de tous les objets.std::list
est pas en cache-friendly, qui est peut-être la chose la plus importante pour les processeurs modernes.La complète parler par Bjarne Stroustrup
Explication approfondie des effets, avec des indicateurs à plusieurs tailles
Noter que cette deuxième lien ici donne quelques situations où vous pouvez peut-être voulez utiliser un
std::list
, comme lorsque la taille des éléments est important. Cependant, j'ai été dans une situation où j'ai de nombreux éléments dans un ordre particulier et nécessaire d'en supprimer certains.Ces éléments ont plus que n'importe quel type, mais pas immense, peut-être 20 à 30 octets chacun sur un ordinateur 32 bits). Le nombre d'éléments a été assez grand pour que toute ma structure de données a été quelques centaines de MiB. La collecte de données est un ensemble de valeurs qui pourraient théoriquement être valide, en se fondant sur les informations connues. L'algorithme d'itération sur tous les éléments et supprimé les éléments qui pourraient ne plus être valides sur la base de nouvelles informations, à chaque passage sans doute la suppression de quelque part autour de 80% du solde des éléments.
Ma première mise en œuvre a été un simple
std::vector
approche où j'ai supprimé les éléments invalides comme je l'ai traversé. Il a travaillé pour de petits ensembles de données de test, mais quand j'ai essayé de faire la vraie chose, il était trop lent pour être utile. Je suis passé à unstd::list
que le conteneur, mais il a utilisé le même algorithme, et j'ai vu une nette amélioration des performances. Cependant, il est encore trop lent pour être utile. La réussite du changement était de revenir à unstd::vector
, mais au lieu de supprimer des éléments qui ont été mauvais, j'ai créé un nouveaustd::vector
, ainsi que tous les éléments que j'ai trouvé étaient bons ont été mis dans cestd::vector
, et puis à la fin de la fonction, je voudrais simplement supprimer l'ancienstd::vector
et utiliser l'autre, et cela m'a donné autant une vitesse de plus de lastd::list
comme lestd::list
m'a donné plus de mon originestd::vector
mise en œuvre, et c'était juste assez rapide pour être utile.Joli coup de gueule, mais je ne vois pas en quoi cela répond à la question à tous. Il existe de nombreuses situations où une seule liste liée fera l'affaire; en fait, je pense que toute mise en œuvre en coulisses utilise essentiellement une seule liste liée d'images pour représenter l'exécution de la pile dans un seul thread (les images peuvent être consécutifs en mémoire, mais ne sont pas de taille égale et ne peuvent donc pas être directement adressée comme dans un vecteur). En règle générale, si vous avez juste besoin d'un simple pile ou de la structure de file d'attente, vous pouvez mettre en place facilement à l'aide d'une liste (et en plus un pointeur vers sa fin dans le cas d'une file d'attente).
Il résout le problème plutôt que de répondre à la question. Le problème, c'est qu'ils veulent une haute performance de la structure de données pour l'insertion et le retrait.
std::forward_list
est rarement la structure de données pour atteindre les objectifs de cette large. Pour votre exemple,std::forward_list
n'aide pas là non plus (sauf si je suis malentendu, je peut très bien être). Si les cadres ne sont pas de taille égale, ce qui implique l'allocation dynamique. Une liste de pointeurs est presque jamais une meilleure structure de données qu'un vecteur de pointeurs.Le meilleur usage que j'ai trouvé pour une simple liste chaînée est pour l'ajout/suppression d'éléments. Les pointeurs peuvent être mises à jour à l'aide de cmpxchg. Bien que je ne crois pas que le std::forward_list fait, malheureusement.
La technique de la traversée du conteneur, la copie dans un nouveau conteneur uniquement ce qui doit être retenu, c'est comment la moderne Java éboueurs de travail.
OriginalL'auteur David Stone
Il n'y a pas de
push_back
parce que la liste n'est pas de garder une trace de la fin de la liste, seul le front.Vous pourriez écrire un wrapper autour de la liste qui maintient un itérateur pour le dernier élément, et met en œuvre
push_back
en utilisant soitinsert_after
oupush_front
selon si la liste est vide. Cela va devenir un peu compliqué si vous souhaitez soutenir les activités les plus complexes (par exemple,sort
etsplice_after
).Alternativement, si vous n'avez pas besoin
push_back
pour être rapide, il est simple de le faire dans le temps linéaire.À moins que la mémoire est extrêmement serré, la meilleure solution est d'utiliser
list
. Cela a les mêmes caractéristiques de performance queforward_list
, et est bidirectionnel, le soutienpush_back
ainsi quepush_front
; le coût est un pointeur par élément.sort
ne serait pas si dur, btw.; juste trier les sous-jacentsforward_list
et de trouver la nouvelle fin. Depuis le tri est O(n lg n), il domine la fin de l'enquête.C'est vrai. Il pourrait être plus difficile à
splice
en temps constant si (si vous êtes épissage normalforward_list
qui n'a pas de piste à sa fin).Oui,
splice
serait une douleur pour obtenir le droit.Je ne crois pas que c'est possible d'avoir à la fois constante de temps
size
et de la constante de tempssplice
, c'est pourquoi il existe des variations dans la complexité des opérations sur unstd::list
. "L'un ou l'autre, mais pas les deux". C'est aussi pourquoi il y a un peu de se plaindre destd::list
arriver constante de temps des garanties poursize
en C++11.OriginalL'auteur Mike Seymour
Le point de std::forward_list est d'être un ultra-stripped-down version de std::list, afin de ne pas stocker un itérateur vers le dernier élément. Si vous en avez besoin, vous devrez l'entretenir vous-même, comme:
OriginalL'auteur Rick Yorgason
Malheureusement je ne peux pas ajouter un commentaire (en bas de la réputation), mais je voulais juste mentionner que l'un des forward_list & liste des avantages est que l'insertion-les opérations de suppression n'invalident pas les itérateurs. J'ai eu une application où la collecte des éléments était de plus en plus lors de l'itération et de la transformation des éléments individuels. L'Absence de itérateur invalidation m'a permis de mettre en œuvre segment d'analyse (début de la liste de départ du segment et de commencer le dernier segment de la fin).
OriginalL'auteur Aleh Karasik