• Il offre la plus petite surconsommation de mémoire, zéro octet par objet contenu ;
  • Il offre l'accès le plus rapide aux éléments contenus ;
  • Il offre les itérateurs les plus rapides et à accès aléatoire ;
  • Les objets adjacents dans le vecteur sont adjacents dans la mémoire, ce qui le rend compatible avec le C, contrairement aux autres conteneurs de la STL.

Si vous avez une bonne raison d'utiliser un autre conteneur que vector, faites le sans vous poser de question, dans le cas contraire, choisissez vector, c'est un bon choix. Les insertions et suppressions ailleurs qu'à la fin ne sont pas optimisées avec ce genre de conteneur car il faut décaler tous les éléments suivants, on les évitera donc autant que possible, mais cela ne doit pas être le critère principal du choix de votre conteneur. On pourra tolérer ces insertions si le nombre d'éléments n'est pas trop élevé mais les performances peuvent se détériorer dramatiquement si le nombre d'éléments augmente... Dans ce cas là, on préfèrera utiliser std::list.

Pour utiliser la classe vector, il suffit d'inclure le fichier d'en-tête vector. Les classes de la STL sont définies dans l'espace de nom std donc on devra préfixer le nom de la classe par std:: à moins que l'on incorpore cet espace de nom dans le fichier au moyen d'un include. C'est cette dernière méthode qui sera utilisée ici pour plus de clarté.

Un bon exemple vaut mieux qu'un long discours.

// Fichier main.cpp
#include <cstdlib>
#include <iostream>
#include <vector>
 
using namespace std;
 
// une classe quelconque pour tests...
class MyClass
{
public:
  MyClass(int v) : val(v) {}
  ~MyClass() {}
  int val;
};
 
// cette fonction reçoit un pointeur sur un espace de mémoire contigüe (ici un 
// vector). ceci démontre la compatibilité des tableaux avec la classe vector.
// on peut lire et modifier les éléments, mais on ne doit ni en supprimer ni
// en ajouter afin de ne pas corrompre la structure interne du vecteur.
void myFunc(int *tab, int size)
{
  cout << "\nacces de type C" << endl;
  for (int i = 0; i < size; ++i)
  {
    cout << "[" << i << "]" << *tab << endl; // on lit le contenu.
    *tab = 2 * i + 5; // on le modifie.
    tab++; // on passe à l'élément suivant.
  }
  cout << "---------------" << endl;
}
 
int main(int argc, char ** argv)
{
  // la classe vector est un template, il suffit d'indiquer le type 
  // qu'elle contiendra entre les < et >. ici, il s'agit du type entier.
  // il peut égalemennt contenir des objets, des pointeurs, des références.
  // il suffit que le type de données contenu possède un constructeur par 
  // copie et l'opérateur d'affectation. ceux par défaut peuvent convenir.
 
  // crée un vecteur d'entiers, sa taille et sa capacité sont de zéro.
  // la taille est le nombre d'éléments contenus dans le vecteur, la capacité 
  // est le nombre total d'éléments qui peuvent être contenus avant qu'un 
  // agrandissement de l'espace mémoire soit nécessaire.
  vector<int> integers01;
 
  if (integers01.empty()) // le vecteur est-il vide ?
    cout << "le vecteur est vide" << endl;
 
  cout << "\n01. taille avant insertions : " << integers01.size() << endl;
  cout << "01. capacite avant insertions : " << integers01.capacity() << endl;
  for (int i = 0; i < 7; ++i)
  {
    // ajout des éléments à la fin du tableau. si l'espace alloué pour le 
    // vecteur est insuffisant push_back alloue la zone mémoire supplémentaire 
    // pour stocker le nouvel élément.
    integers01.push_back(i);
  }
 
  int premier = integers01.front(); // accès au premier élément.
  int dernier = integers01.back(); // accès au dernier élément.
 
  // on note que la capacité est toujours supérieure ou égale à la taille.
  cout << "01. taille apres insertions : " << integers01.size() << endl;
  cout << "01. capacite apres insertions : " << integers01.capacity() << endl;
 
 
  vector<int> integers02;
  // on réserve une capacité de 10 éléments. ceci ne change ni la taille du 
  // vecteur, ni les valeurs contenues.
  integers02.reserve(10);
  // la taille est de zéro puisqu'il n'y a pas d'élément
  cout << "\n02. taille : " << integers02.size() << endl;
  // la capacité est de 10, on peut donc faire 10 insertions avant que le 
  // vecteur ne se redimensionne automatiquement.
  cout << "02. capacite : " << integers02.capacity() << endl;
 
  // on initialise la taille à la création du vecteur. ceci engendre la création
  // de 10 éléments dans la liste. ces éléments sont construits via leur 
  // constructeur par défaut, d'où leurs valeurs égales à zéro pour les entiers.
  vector<int> integers03(10);
  cout << "\n03. taille : " << integers03.size() << endl;
  cout << "03. capacite : " << integers03.capacity() << endl;
 
  // accès aux éléments via l'opérateur [] comme pour un tableau.
  const vector<int>::size_type size = integers03.size();
  cout << "\nacces via l'operateur []" << endl;
  for (vector<int>::size_type i = 0; i < size; ++i)
  {
    // en lecture.
    cout << "[" << i << "] = " << integers03[i] << endl;
    // ou en écriture.
    integers03[i] = i * i - i;
  }
  cout << "------------------------" << endl;
 
  // accès aux éléments via un itérateur, méthode plus générique.
  vector<int>::const_iterator end = integers03.end();
  cout << "\nacces via un iterateur" << endl;
  for (vector<int>::iterator it = integers03.begin(); it < end; ++it)
  {
    // en lecture.
    cout << *it << endl;
    // ou en écriture.
    *it = *it / 2 + 5; 
  }
  cout << "---------------------" << endl;
 
  // http://www.cplusplus.com/reference/stl/vector/at.html
  // il est préférable d'utiliser la méthode at() plutôt que l'opérateur [].
  // en cas d'index > size() l'exception out_of_range est levée.
  try
  {
    // sans ce try/catch, le programme aurait planté...
    integers03.at(20) = 7;
  }
  catch (out_of_range &e)
  {
    cout << "exception levee : " << e.what() << endl;
  }
 
  // comme la mémoire du vector est organisée en un seul bloc, il est possible 
  // de passer un pointeur sur son premier élément à une fonction qui attendrait 
  // un tableau dynamique (en C par exemple). on doit également lui transmettre 
  // sa taille afin de ne pas faire de débordement mémoire.
  myFunc(&integers03.begin()[0], integers03.size());
 
 
  // recopier des vecteurs
  // il ne faut pas recopier manuellement des vecteurs via des boucles.
  // tout est prévu pour effectuer ce genre de tâche. on utilise le 
  // constructeur par copie. on peut lui passer un vecteur en paramètre 
  // ou alors deux itérateurs : le premier correspondant au premier 
  // élément à copier et le second le dernier à copier (exclu).
 
  // le constructeur par copie effectue une copie de tous les éléments 
  // contenus. attention, dans le cas de pointeurs, seuls les pointeurs 
  // sont copiés, pas la mémoire pointée...
  vector<int> copyIntegers01(integers01);
 
  // ici, copie du tableau en entier. notez que 
  // integers02.end() == integers02.begin() + integers02.size(). le dernier 
  // élément est exclu. pour ne copier que le premier élément, on pourra faire 
  // vector<int> test(integers02.begin(), integers02.begin() + 1)...
  vector<int> copyIntegers02(integers02.begin(), integers02.end());
 
  // cette instruction vide et remplace les éléments de copyIntegers02 par ceux 
  // de integers01 dans l'intervalle donné par les deux itérateurs.
  // on peut également faire copyIntegers02 = integers01;
  copyIntegers02.assign(integers01.begin(), integers01.end());
  copyIntegers02 = integers01; // instruction equivalente.
 
  // fonctionnement identique avec les tableaux.
  int tab[] = {1, 2, 3, 4, 5};
  copyIntegers02.assign(&tab[0], &tab[4]);
 
 
  vector<MyClass *> classes;
  // ajout des pointeurs sur les objets + allocation mémoire.
  for (unsigned int i = 0; i < 10; ++i)
    classes.push_back(new MyClass(i));
 
  // utilisation de ces objets.
  classes[0]->val = 45;
  // ...
 
  // destruction des éléments pointés.
  for (unsigned int i = 0; i < classes.size(); ++i)
    delete classes[i];
 
  // on vide le vecteur pour éviter d'utiliser son contenu par erreur ou de 
  // le détruire une seconde fois, puisqu'il n'est plus valide.
  classes.clear();
 
  return EXIT_SUCCESS;
}

La méthode reserve() permet une réallocation manuelle. La principale raison de l'utiliser est l'efficacité : si vous connaissez à l'avance le nombre d'éléments que le vecteur devra contenir, il est plus efficace d'allouer cette taille d'un coup plutôt que de compter sur la réallocation automatique. Lorqu'une réallocation de mémoire est faite, tous les éléments contenus sont alors copiés dans le nouvel espace mémoire, bien choisir cet espace initial est donc synonyme de performances. Une autre raison de l'utiliser est de pouvoir invalider les itérateurs en cours d'utilisation. Ces derniers sont d'ailleurs également invalidés lors de l'insertion ou la suppression d'un élément dans le vecteur.

Quand on insère ou quand on récupère une valeur d'un vecteur, il s'agit d'une copie, c'est pourquoi il est beaucoup plus judicieux d'y stocker des pointeurs : ce sont les pointeurs qui sont copiés et non les données pointées. De ce fait, le vecteur est moins gros, mais n'oubliez pas de libérer la mémoire, car le vecteur ne le fera pas pour vous lors de sa destruction. Le vecteur détruit les objets qu'il contient, mais pas la mémoire pointée par les pointeurs. Si plusieurs vecteurs se partagent les mêmes pointeurs, on utilisera des shared_ptr.

Vous pourrez approfondir le sujet avec la sélection des livres suivants : Generic Programming and the Stl, Pour mieux développer avec C++, Effective STL et STL précis & concis.