std :: vector versus std :: array en C ++

283

Quelle est la différence entre un std::vectoret un std::arrayen C ++? Quand faut-il préférer un autre? Quels sont les avantages et les inconvénients de chacun? Tout ce que mon manuel fait est de lister comment ils sont les mêmes.

Zud
la source
1
Je cherche une comparaison de std::vectorvs std::arrayet comment les termes sont différents.
2010
Zud, std::arrayn'est pas la même chose qu'un tableau C ++. std::arrayest un wrapper très fin autour des tableaux C ++, dont le but principal est de cacher le pointeur à l'utilisateur de la classe. Je mettrai à jour ma réponse.
ClosureCowboy
J'ai mis à jour le titre et le texte de la question pour refléter votre clarification.
Matteo Italia

Réponses:

318

std::vectorest une classe de modèle qui encapsule un tableau dynamique 1 , stocké dans le tas, qui grandit et se rétrécit automatiquement si des éléments sont ajoutés ou supprimés. Il fournit tous les crochets ( begin(), end(), itérateurs, etc.) qui le font fonctionner très bien avec le reste de la STL. Il dispose également de plusieurs méthodes utiles qui vous permettent d'effectuer des opérations qui, sur un tableau normal, seraient fastidieuses, comme par exemple l'insertion d'éléments au milieu d'un vecteur (il gère tout le travail de déplacement des éléments suivants dans les coulisses).

Puisqu'il stocke les éléments en mémoire alloués sur le tas, il a une surcharge en ce qui concerne les tableaux statiques.

std::arrayest une classe de modèle qui encapsule un tableau de taille statique, stocké à l'intérieur de l'objet lui-même, ce qui signifie que si vous instanciez la classe sur la pile, le tableau lui-même sera sur la pile. Sa taille doit être connue au moment de la compilation (elle est transmise en tant que paramètre de modèle), et elle ne peut pas augmenter ou diminuer.

Il est plus limité que std::vector, mais il est souvent plus efficace, en particulier pour les petites tailles, car en pratique, il s'agit principalement d'un wrapper léger autour d'un tableau de style C. Cependant, il est plus sécurisé, car la conversion implicite en pointeur est désactivée, et il fournit une grande partie des fonctionnalités liées à STL std::vectoret des autres conteneurs, vous pouvez donc l'utiliser facilement avec les algorithmes & co STL. Quoi qu'il en soit, pour la limitation même de la taille fixe, il est beaucoup moins flexible que std::vector.

Pour une introduction à std::array, consultez cet article ; pour une introduction rapide à std::vectoret aux opérations qui y sont possibles, vous pouvez consulter sa documentation .


  1. En fait, je pense que dans la norme elles sont décrites en termes de complexité maximale des différentes opérations (ex: accès aléatoire en temps constant, itération sur tous les éléments en temps linéaire, ajout et suppression d'éléments à la fin en temps amorti constant, etc), mais AFAIK il n'y a pas d'autre méthode pour répondre à ces exigences que d'utiliser un tableau dynamique. Comme indiqué par @Lucretiel, la norme exige en fait que les éléments soient stockés de manière contiguë, il s'agit donc d' un tableau dynamique, stocké là où l'allocateur associé le place.
Matteo Italia
la source
6
Concernant votre note de bas de page: Bien que cela soit vrai, la norme garantit également que l'arithmétique du pointeur sur les éléments internes fonctionne, ce qui signifie qu'il doit être un tableau: & vec [9] - & vec [3] == 6 est vrai.
Lucretiel
9
Je suis presque sûr que ce vecteur ne rétrécit pas automatiquement, mais depuis C ++ 11, vous pouvez appeler shrink_to_fit.
Dino
Je suis totalement confus par le terme tableau statique et je ne sais pas quelle est la bonne terminologie. Vous voulez dire un tableau de taille statique et non un tableau de variables statiques (un utilisant un stockage statique). stackoverflow.com/questions/2672085/… . Quelle est la bonne terminologie? Un tableau statique est-il un terme bâclé pour un tableau de taille fixe?
Z boson du
3
@Zboson: ce n'est certainement pas seulement vous, statique est un terme assez abusé; le staticmot-clé même en C ++ a trois significations différentes sans rapport, et le terme est également souvent utilisé pour parler de choses qui sont fixées au moment de la compilation. J'espère que "de taille statique" est un peu plus clair.
Matteo Italia
3
Une chose à noter: pour la programmation en temps réel (où vous n'êtes pas censé avoir d' allocation / désallocation dynamique après le démarrage), std :: array serait probablement préféré à std :: vector.
TED
17

Utilisation de la std::vector<T>classe:

  • ... est aussi rapide que d'utiliser des tableaux intégrés, en supposant que vous ne faites que ce que les tableaux intégrés vous permettent de faire (lire et écrire sur des éléments existants).

  • ... se redimensionne automatiquement lorsque de nouveaux éléments sont insérés.

  • ... vous permet d'insérer de nouveaux éléments au début ou au milieu du vecteur, "décalant" automatiquement le reste des éléments "vers le haut" (cela a-t-il un sens?). Il vous permet de supprimer des éléments n'importe où dans le std::vector, en déplaçant automatiquement le reste des éléments vers le bas.

  • ... vous permet d'effectuer une lecture vérifiée par plage avec la at()méthode (vous pouvez toujours utiliser les indexeurs []si vous ne voulez pas que cette vérification soit effectuée).

Il y a deux trois mises en garde principales à utiliser std::vector<T>:

  1. Vous n'avez pas un accès fiable au pointeur sous-jacent, ce qui peut être un problème si vous traitez avec des fonctions tierces qui demandent l'adresse d'un tableau.

  2. La std::vector<bool>classe est idiote. Il est implémenté comme un champ binaire condensé, pas comme un tableau. Évitez-le si vous voulez un tableau de bools!

  3. Pendant l'utilisation, les std::vector<T>s vont être un peu plus grands qu'un tableau C ++ avec le même nombre d'éléments. En effet, ils doivent garder une trace d'une petite quantité d'autres informations, telles que leur taille actuelle, et parce que chaque fois std::vector<T>qu'ils redimensionnent, ils réservent plus d'espace qu'ils n'en ont besoin. Cela leur évite d'avoir à redimensionner chaque fois qu'un nouvel élément est inséré. Ce comportement peut être modifié en fournissant une personnalisation allocator, mais je n'ai jamais ressenti le besoin de le faire!


Edit: Après avoir lu la réponse de Zud à la question, j'ai pensé que je devrais ajouter ceci:

La std::array<T>classe n'est pas identique à un tableau C ++. std::array<T>est un wrapper très fin autour des tableaux C ++, dans le but principal de cacher le pointeur à l'utilisateur de la classe (en C ++, les tableaux sont implicitement convertis en pointeurs, souvent avec un effet consternant). La std::array<T>classe stocke également sa taille (longueur), ce qui peut être très utile.

FermetureCowboy
la source
5
C'est «aussi rapide» que d'utiliser un tableau intégré alloué dynamiquement. D'un autre côté, l'utilisation d'un tableau automatique peut avoir des performances considérablement différentes (et pas seulement pendant l'allocation, en raison des effets de localité).
Ben Voigt
4
Pour les vecteurs non booléens en C ++ 11 et versions ultérieures, vous pouvez appeler data()un std::vector<T>pour obtenir le pointeur sous-jacent. Vous pouvez également simplement prendre l'adresse de l'élément 0 (garanti pour fonctionner avec C ++ 11, fonctionnera probablement avec les versions antérieures).
Matt
16

Pour souligner un point soulevé par @MatteoItalia, la différence d'efficacité est l'endroit où les données sont stockées. La mémoire de tas (requise avec vector) nécessite un appel au système pour allouer de la mémoire et cela peut être coûteux si vous comptez des cycles. La mémoire de pile (possible pour array) est virtuellement "sans surcharge" en termes de temps, car la mémoire est allouée en ajustant simplement le pointeur de pile et elle n'est effectuée qu'une seule fois lors de l'entrée dans une fonction. La pile évite également la fragmentation de la mémoire. Pour être sûr, std::arrayne sera pas toujours sur la pile; cela dépend de l'endroit où vous l'allouez, mais cela impliquera toujours une allocation de mémoire de moins du tas par rapport au vecteur. Si tu as un

  • petit "tableau" (moins de 100 éléments disent) - (une pile typique est d'environ 8 Mo, alors n'allouez pas plus de quelques Ko sur la pile ou moins si votre code est récursif)
  • la taille sera fixe
  • la durée de vie est dans la portée de la fonction (ou est une valeur membre avec la même durée de vie que la classe parent)
  • vous comptez les cycles,

certainement utiliser un std::arraysur un vecteur. Si l'une de ces exigences n'est pas vraie, utilisez a std::vector.

Mark Lakata
la source
3
Bonne réponse. "Pour être sûr, std :: array ne sera pas toujours sur la pile; cela dépend de l'endroit où vous l'allouez" Alors, comment pourrais-je créer un std :: array pas sur la pile avec un grand nombre d'éléments?
Trilarion
4
@Trilarion utilise new std::arrayou en fait un membre d'une classe que vous utilisez «nouveau» pour allouer.
Mark Lakata
Donc, cela signifie new std::arrayqu'il s'attend toujours à connaître sa taille au moment de la compilation et ne peut pas changer sa taille mais vit toujours sur le tas?
Trilarion
Oui. Il n'y a pas un avantage significatif à l' utilisation new std::arrayvs new std::vector.
Mark Lakata
12

Si vous envisagez d'utiliser des tableaux multidimensionnels, il existe une différence supplémentaire entre std :: array et std :: vector. Un tableau std :: array multidimensionnel aura les éléments emballés en mémoire dans toutes les dimensions, tout comme le tableau de style alternatif. Un std :: vector multidimensionnel ne sera pas compressé dans toutes les dimensions.

Compte tenu des déclarations suivantes:

int cConc[3][5];
std::array<std::array<int, 5>, 3> aConc;
int **ptrConc;      // initialized to [3][5] via new and destructed via delete
std::vector<std::vector<int>> vConc;    // initialized to [3][5]

Un pointeur vers le premier élément du tableau de style c (cConc) ou du tableau std :: (aConc) peut être itéré dans tout le tableau en ajoutant 1 à chaque élément précédent. Ils sont bien emballés.

Un pointeur vers le premier élément du tableau vectoriel (vConc) ou du tableau de pointeurs (ptrConc) ne peut être itéré que par les 5 premiers éléments (dans ce cas), puis il y a 12 octets (sur mon système) de surcharge pour le vecteur suivant.

Cela signifie qu'un tableau std :: vector> initialisé en tant que tableau [3] [1000] sera beaucoup plus petit en mémoire qu'un tableau initialisé en tant que tableau [1000] [3], et les deux seront plus grands en mémoire qu'un std: tableau alloué de toute façon.

Cela signifie également que vous ne pouvez pas simplement passer un tableau vectoriel (ou pointeur) multidimensionnel à, disons, openGL sans tenir compte de la surcharge de mémoire, mais vous pouvez naïvement passer un tableau std :: array multidimensionnel à openGL et le faire fonctionner.

psimpson
la source
-17

Un vecteur est une classe de conteneur tandis qu'un tableau est une mémoire allouée.

Saif al Harthi
la source
23
Votre réponse semble porter sur std::vector<T>versus T[], mais la question porte sur std::vector<T>versus std::array<T>.
Keith Pinson