Podstawowym udogodnieniem dostarczanym przez bibliotekę standardową są kolekcje. Kolekcja jest strukturą danych złożoną z danych pewnego typu i wyposażoną w zestaw operacji, jakie na tych danych można wykonywać. Może to być dodawanie elementów, ich usuwanie, wyszukiwanie, porównywanie, kopiowanie, porządkowanie itd. W bibliotece standardowej zaimplementowano takie podstawowe struktury danych, jak listy, stos, kolejki, kopce, tablice asocjacyjne (słowniki), zbiory; korzystając z nich użytkownik może łatwo i efektywnie tworzyć własne, bardziej złożone struktury, jak choćby różnego typu drzewa czy grafy.
Najprostsza kolekcja opisywana jest szablonem vector dołączanym poprzez włączenie nagłówka vector. Modelem wektora jest tablica elementów tego samego typu, ale o nieokreślonej zawczasu pojemności. Elementy są umieszczane w dobrze zdefiniowanym porządku i mamy do nich dostęp swobodny, to znaczy znając pozycję (indeks) elementu możemy pobrać jego wartość lub go zmienić czy usunąć bez konieczności przeglądania wszystkich elementów wektora. Elementy możemy dodawać na dowolnej pozycji, choć najefektywniejsze (w stałym, niezależnym od rozmiaru wektora, czasie) jest dodawanie na koniec. W miarę jak elementów przybywa, wektorowi przydzielana jest automatycznie większa pamięć — programista nie musi się już martwić zarządzaniem pamięcią, jej przydzielaniem i zwalnianiem. Ponieważ vector jest szablonem klasy, a nie klasą, obiekty-wektory możemy tworzyć konkretyzując wzorzec dla pewnego typu — może to być również typ wbudowany, jak int czy double. Domyślny konstruktor tworzy wektor pusty (są też inne konstruktory). Do utworzonego wektora dodajemy elementy (takiego typu, jak ten dla którego wzorzec został skonkretyzowany) za pomocą metody push_back, która dodaje kopię obiektu na końcu wektora. Kopię, a zatem trzeba zadbać, aby obiekt był dobrze „kopiowalny”, a więc na przykład miał prawidłowo zdefiniowany konstruktor kopiujący.
Elementy można pobierać na różne sposoby. Najprościej to zrobić
za pomocą indeksowania: robimy to dokładnie tak jak dla normalnej
tablicy (pierwszy element ma, jak zwykle, indeks zero). Taki sposób
jest bardzo efektywny, ale nie jest sprawdzane, czy użyty indeks jest
legalny (tak jak nie jest to sprawdzane w przypadku normalnych tablic).
Jeśli chcemy, aby zostało sprawdzone, czy indeks mieści się
w dozwolonym zakresie, czyli od zera do liczby o jeden mniejszej niż
aktualny rozmiar wektora, to można użyć metody
at
podając indeks jako argument. Przy tej metodzie
dostępu zgłaszany jest wyjątek
out_of_range
 (z nagłówka
stdexcept), gdy indeks jest nielegalny.
Wyjątek ten możemy obsłużyć:
      1.  #include <vector>
      2.  #include <stdexcept>
      3.  #include <iostream>
      4.  #include <string>
      5.  using namespace std;
      6.  
      7.  int main() {
      8.      vector<string> vs;
      9.  
     10.      vs.push_back("Ola");
     11.      vs.push_back("Ula");
     12.      vs.push_back("Ela");
     13.      vs.push_back("Ala");
     14.  
     15.      try {
     16.          for ( int i = 0; i < 5 /* BLAD */; i++ )
     17.              cout << vs.at(i) << " " ;
     18.      } catch(out_of_range) {
     19.          cout << "\n*** Zly indeks! *** "
     20.               << " wektor ma tylko " << vs.size()
     21.               << " elementy!" << endl;
     22.      }
     23.      cout << endl;
     24.  
     25.      cout << "Pierwszy element: " << vs.front() << endl;
     26.      cout << "Ostatni  element: " << vs.back()  << endl;
     27.  
     28.      vs.pop_back();
     29.  
     30.      cout << "Po pop_back: ";
     31.      int size = (int)vs.size();
     32.      for ( int i = 0; i < size; i++)
     33.          cout << vs[i] << " " ;
     34.      cout << endl;
     35.  }
    Ola Ula Ela Ala
    *** Zly indeks! ***  wektor ma tylko 4 elementy!
    Pierwszy element: Ola
    Ostatni  element: Ala
    Po pop_back: Ola Ula Ela
Jak widzimy (linia 8), obiekt
vs
 jest obiektem klasy
vector<string>
 powstałej z konkretyzacji szablonu
vector
 dla typu
string.
Kolekcja jest zatem kolekcją elementów, z których każdy jest obiektem klasy string. Metoda size, użyta w linii 31 zwraca, jak łatwo się domyślić, rozmiar kolekcji (nie tylko wektorów). Typem zwracanym jest pewien typ całowity bez znaku, którego aliasem jest nazwa vector::size_type — dlatego użyliśmy rzutowania — można tu było użyć zwykłego int'a, ale narazilibyśmy się na ostrzeżenia kompilatora. Metoda pop_back (linia 28) usuwa ostatni element kolekcji, ale go nie zwraca. Metody front i back (linie 25 i 26) zwracają, przez referencję, pierwszy i ostatni element kolekcji bez ich usuwania.
Podstawowym narzędziem do wykonywania operacji na kolekcjach są iteratory. Pozwalają one poruszać się po elementach kolekcji w sposób niezależny od ich rzeczywistego typu. Są swego rodzaju uogólnieniem wskaźników. Ich typ jest zdefiniowany w definicji wzorca szablonu kolekcji za pomocą instrukcji typedef i ma postać kol<Typ>::iterator, gdzie kol jest nazwą szablonu kolekcji, a Typ jest typem użytym do skonkretyzowania tego szablonu. Użytkownik nie musi wiedzieć, jaki naprawdę ten typ jest. Na przykład nazwą (aliasem) typu iteratora związanego z wektorem liczb typu int jest vector<int>::iterator. Kolekcja może być kolekcją elementów niemodyfikowalnych; dla każdej kolekcji zatem jest też zdefiniowany osobny typ const_iterator (na przykład vector<int>::const_iterator).
Iteratory mają semantykę wskaźników do elementów tablicy, to znaczy, jeśli it jest iteratorem wskazującym na pewien element kolekcji, to *it jest l-wartością wskazywanego elementu, a it->sklad jest nazwą składowej sklad wskazywanego przez iterator obiektu (jeśli taka składowa istnieje). Podobnie, po ' ++it' iterator it wskazuje na następny element kolekcji.
Istnieją w klasach kolekcyjnych dwie ważne bezargumentowe metody
zwracające iteratory: metoda
begin
 i 
end. Metoda
begin zwraca iterator
wskazujący na pierwszy element kolekcji. Nieco bardziej skomplikowany
jest wynik metody
end.
Nie jest to iterator wskazujący na ostatni element kolekcji, jak
by się mogło wydawać, ale na nieistniejący element „tuż za
ostatnim”. Jeśli przebiegamy kolekcję za pomocą iteratora, to
osiągnięcie przez niego wartości zwracanej przez
end
oznacza, że przebiegliśmy już całą kolekcję.
Zamiast więc sprawdzać, jak zwykle w pętlach przebiegających
tablice, czy indeks jest mniejszy od wymiaru tablicy, sprawdzamy,
czy iterator wciąż nie jest równy iteratorowi zwracanemu przez
metodę
end; jeśli jest, to pętlę należy przerwać,
bo iterator wskazuje na element nieistniejący:
      1.  #include <vector>
      2.  #include <iostream>
      3.  #include <string>
      4.  using namespace std;
      5.  
      6.  int main() {
      7.      vector<string> vs;
      8.  
      9.      vs.push_back("Ola");
     10.      vs.push_back("Ula");
     11.      vs.push_back("Ela");
     12.      vs.push_back("Ala");
     13.  
     14.      for ( vector<string>::iterator ite = vs.begin();
     15.                                     ite!= vs.end(); ++ite)
     16.          cout << *ite << " ";
     17.      cout << endl;
     18.  
     19.      // albo
     20.  
     21.      vector<string>::iterator it, kon = vs.end();
     22.  
     23.      for ( it = vs.begin(); it != kon; ++it )
     24.          cout << *it << " ";
     25.      cout << endl;
     26.  
     27.      // albo
     28.  
     29.      typedef vector<string>::iterator SIT;
     30.  
     31.      SIT iter, koniec = vs.end();
     32.  
     33.      for ( iter = vs.begin(); iter != koniec; ++iter )
     34.          cout << *iter << " ";
     35.      cout << endl;
     36.  }
Jeśli kolekcja jest niemodyfikowalna, to nie tylko możemy, ale
musimy użyć iteratora typu
const_iterator.
 Z taką sytuacją
spotykamy się bardzo często, gdy przesyłamy kolekcję jako argument
funkcji, która z założenia nie powinna elementów tej kolekcji
zmieniać, a wobec tego jej parametr jest typu ustalonego:
      1.  #include <vector>
      2.  #include <iostream>
      3.  using namespace std;
      4.  
      5.  struct Person {
      6.      char name[20];
      7.      int  year;
      8.      void print() const {
      9.          cout << name << "-" << year << endl;
     10.      }
     11.  } john = {"John",25}, mary = {"Mary",18}, sue = {"Sue",9};
     12.  
     13.  
     14.  void printPerson(const vector<const Person*>& list) {
     15.      typedef vector<const Person*>::const_iterator IT;
     16.      IT itend = list.end();
     17.      for ( IT it = list.begin(); it != itend; it++ )
     18.          (*it)->print();
     19.  }
     20.  
     21.  int main() {
     22.      vector<const Person*> list;
     23.  
     24.      list.push_back(&john);
     25.      list.push_back(&mary);
     26.      list.push_back(&sue);
     27.  
     28.      printPerson(list);
     29.  }
vector<const Person*>oznacza, że kolekcja jest wektorem wskaźników do ustalonych elementów, a zatem, że poprzez odwołania się do obiektów klasy Person wskazywanych przez elementy kolekcji nie można zmienić tych obiektów. Z kolei pierwsze const w określeniu typu parametru funkcji printPerson oznacza, że jest to kolekcja niemodyfikowalnych elementów, czyli niemodyfikowalnych wskaźników, bo taki jest typ tych elementów. A zatem wewnątrz funkcji printPerson nie można zmienić ani adresów zapisanych we wskaźnikach będących elementami kolekcji, ani obiektów przez te wskaźniki wskazywanych. Ponieważ funkcja, poprzez typ zadeklarowanego parametru, „obiecała”, że nie zmieni obiektów wskazywanych, a wywołuje na rzecz tych obiektów metodę print, metoda ta musiała być zadeklarowana jako const. Funkcja „obiecała” też, że nie zmieni elementów kolekcji, czyli wskaźników, zatem użyty iterator musiał tu być typu const_iterator.
Zauważmy jeszcze, że parametr funkcji printPerson jest typu referencyjnego. Jest to zwykle pożądane przy przesyłaniu do funkcji kolekcji — w przeciwnym przypadku kopiowane byłyby całe kolekcje, wraz ze wszystkimi elementami. Tego problemu nie było przy przesyłaniu tablic, kiedy tak naprawdę przesyłany był tylko wskaźnik do pierwszego elementu.
Prócz iteratorów
iterator
 i 
const_iterator
 istnieje typ
iteratora odwrotnego
reverse_iterator,
 za pomocą którego można przebiegać
kolekcje w odwrotnym kierunku; na przykład program
      1.  #include <vector>
      2.  #include <iostream>
      3.  #include <string>
      4.  using namespace std;
      5.  
      6.  int main() {
      7.      vector<string> vs;
      8.  
      9.      vs.push_back("Ola");
     10.      vs.push_back("Ula");
     11.      vs.push_back("Ela");
     12.      vs.push_back("Ala");
     13.  
     14.      typedef vector<string>::iterator         DO_PRZODU;
     15.      typedef vector<string>::reverse_iterator DO_TYLU;
     16.  
     17.      DO_PRZODU piter, pkoniec = vs.end();
     18.      DO_TYLU   titer, tkoniec = vs.rend();
     19.  
     20.      for ( piter = vs.begin(); piter != pkoniec; piter++ )
     21.          cout << *piter << " ";
     22.      cout << endl;
     23.  
     24.      for ( titer = vs.rbegin(); titer != tkoniec; titer++ )
     25.          cout << *titer << " ";
     26.      cout << endl;
     27.  }
    Ola Ula Ela Ala
    Ala Ela Ula Ola
Istnieje też
const_reverse_iterator
do przebiegania kolekcji elementów ustalonych w kierunku od końca do
początku. Zauważmy, że dla iteratorów odwrotnych metody
begin
 i 
end
 trzeba zastąpić metodami
rbegin
i 
rend.
Ze względu na swoją funkcjonalność iteratory dzielą się na kilka kategorii. Z różnymi kolekcjami związane są różne kategorie iteratorów.
Na przykład iterator związany z wektorem (vector) pozwala nie tylko na przesuwanie się w obu kierunkach za pomocą operatorów zwiększania i zmniejszania, ale na stosowanie arytmetyki wskaźników, to znaczy np. dodanie liczby 3 do takiego iteratora daje iterator przesunięty o trzy elementy do przodu względem wyjściowego. Podobnie, po
       vector<string> vs;
       // ...
       vector<string>::iterator it = vs.begin() + vs.size()/2;
iterator
it
 wskazuje na środkowy element wektora. Iteratory
takie można też porównywać za pomocą operatorów relacyjnych (jak
np. '
>') czy odejmować (wynikiem jest liczba elementów
pomiędzy pozycjami w kolekcji wskazywanymi przez te iteratory).
Elementy wskazywane takimi iteratorami można odczytywać, jak i na nie
przypisywać. Tego typu iteratory to
iteratory o dostępie bezpośrednim
 (ang. random access
iterator). Są one np. związane z wektorami (vector),
 kolejkami dwustronnymi (deque)
 i obiektami klasy
string,
 które są traktowane jako kolekcje znaków.
Drugim typem iteratora jest iterator dwukierunkowy (ang. bidirectional iterator). Pozwala na poruszanie się po kolekcji w obu kierunkach za pomocą operatorów zwiększania i zmniejszania ('++' i '- -'), ale nie wspiera arytmetyki wskaźników, na przykład dodawania liczb całkowitych do iteratora. Takie itertory można porównywać za pomocą ' ==' i ' !=', ale nie za pomocą ' <' i ' >'. Są one związane na przykład z kolekcjami listowymi (list, z nagłówka list).
Jeszcze bardziej ograniczona jest funkcjonalność iteratorów jednokierunkowych (ang. forward iterator). Są one podobne do dwukierunkowych, ale pozwalają na poruszanie się tylko w jednym kierunku: do przodu.
W końcu najbardziej ograniczone są możliwości iteratorów wejściowych i wyjściowych (ang. input i output iterator). Pozwalają one tylko na jednokrotny przebieg (ang. single pass) kolekcji w kierunku do przodu. Iteratory wejściowe pozwalają tylko na odczyt elementu wskazywanego, a wyjściowe tylko na przypisanie im wartości, ale nie odczyt. Takie iteratory związane są zwykle ze strumieniami (wejściowymi i wyjściowymi).
Iteratory są podstawowym elementem stosowanym w algorytmach i funkcjach operujących na kolekcjach. Pełnią one rolę łącznika między algorytmami i funkcjami a strukturami danych, jakimi są kolekcje.
Na kolekcjach można wykonywać wiele operacji, choć należy
pamiętać, że nie każdą z nich można wykonać na każdej
kolekcji. Związane jest to z kwestią wydajności: niektóre
kolekcje dopuszczają mniej operacji, ale za to są one wykonywane
bardzo efektywnie. Na przykład, jak wspominaliśmy, różnica dwóch
iteratorów odnoszących się do wektora daje liczbę elementów
pomiędzy nimi — ponieważ w wektorze elementy są rozmieszczone
w pamięci jeden przy drugim i rozmiar ich jest znany, jest to operacja
bardzo szybka, sprowadzająca się do jednego odejmowania i jednego
dzielenia. Dla listy (list) nie jest to takie proste, więc
aby osiągnąć ten sam cel, należy użyć specjalnej funkcji
distance
o liniowym (a więc proporcjonalnym do rozmiaru listy) czasie
działania:
      1.  #include <iostream>
      2.  #include <vector>
      3.  #include <string>
      4.  #include <list>
      5.  using namespace std;
      6.  
      7.  int main() {
      8.      vector<string> vec;
      9.  
     10.  #if   defined(__WIN32)
     11.      cout << "Lista slow (^Z konczy): ";
     12.  #elif defined(__linux)
     13.      cout << "Lista slow (^D konczy): ";
     14.  #else
     15.      #error Nieznany system
     16.  #endif
     17.  
     18.      string s;
     19.      while ( cin >> s ) vec.push_back(s);
     20.      cin.clear();
     21.  
     22.      list<string> lis(vec.begin(),vec.end());
     23.  
     24.      cout << "Slowo do znalezienia: ";
     25.      cin  >> s;
     26.  
     27.      vector<string>::iterator sit;
     28.      list<string>::iterator   lit;
     29.  
     30.        // wektor
     31.      for (sit = vec.begin(); sit != vec.end(); ++sit)
     32.          if ( *sit == s ) break;
     33.      if ( sit != vec.end() )
     34.          cout << "Slowo " << s << " na pozycji "
     35.               << sit - vec.begin() << endl;
     36.      else
     37.          cout << "Slowo " << s << " nie wystapilo" << endl;
     38.  
     39.        // lista
     40.      for (lit = lis.begin(); lit != lis.end(); ++lit)
     41.          if ( *lit == s ) break;
     42.      if ( lit != lis.end() )
     43.          cout << "Slowo " << s << " na pozycji "
     44.               << distance(lis.begin(),lit) << endl;
     45.      else
     46.          cout << "Slowo " << s << " nie wystapilo" << endl;
     47.  }
    Lista slow (^D konczy): Ala
    Ela
    Ola
    Ula
    ^D
    Slowo do znalezienia: Ola
    Slowo Ola na pozycji 2
    Slowo Ola na pozycji 2
Do przerwania wczytywania danych (linia 20) użyty tu został
znak końca danych, czyli Ctrl-D (pod Windows byłoby to Ctrl-Z).
Zwykła tablica może być traktowana jak kolekcja. Wskaźniki do elementów tablicy pełnią wtedy rolę iteratorów. Na przykład po
       int arr[] = {1,4,6,8,9};
       vector<int> v(arr+1,arr+4);
wektor 
v
 będzie zawierał liczby 4, 6 i 8 bo wskaźnik
arr+1 wskazuje na liczbę 4, a wskaźnik
arr+4 na
liczbę 9, a zgodnie z tym, co mówiliśmy, przedział obejmuje element
wskazywany przez iterator pierwszy, ale już nie obejmuje elementu
wskazywanego przez iterator drugi.
Jak wspomnieliśmy, kolekcją (znaków) jest obiekt klasy string. Kilka przykładów ilustrujących tę cechę napisów w stylu C++ podaliśmy już w rozdziale o napisach .
Inną, często stosowaną operacją jest usuwanie elementów kolekcji. Służy do tego metoda erase. Jej argumentem powinien być iterator wskazujący na usuwany element albo para iteratorów; w tym drugim przypadku usuwane są elementy od wskazywanego pierwszym iteratorem włącznie do wskazywanego drugim iteratorem wyłącznie. Na przykład po
       vector<Person> os;
       os.push_back(Person("Jenny"));
       os.push_back(Person("Jill"));
       os.push_back(Person("Jane"));
       os.push_back(Person("Janet"));
       os.erase(os.begin()+1, os.end());
z wektora
os
 usunięte zostaną wszystkie elementy
prócz pierwszego. Elementów nie należy usuwać w pętli, bo
po usunięciu pierwszego z nich stan kolekcji się zmienia — 
pozostałe elementy zmieniają swoje pozycje, co może doprowadzić do
chaosu.
Podobnie można do kolekcji dodawać nowe elementy za pomocą metody insert. Argumentem wskazującym, na której pozycji (a ściślej, przed którym elementem) nowe elementy powinny się pojawić, jest oczywiście iterator. Z kolei elementy do wstawienia mogą pochodzić z innej kolekcji; ich zakres wskazywany jest parą iteratorów:
       double tab[] = {2, 4, 6, 7, 8, 1.5, 5, 7};
       list<double> lis(5);
       lis.insert(lis.begin(),10.5);
       lis.insert(lis.begin(),tab+1,tab+3);
W tym fragmencie zwróćmy uwagę na linię drugą. Tworzymy tu listę
o pięciu elementach, którym nadawane są wartości domyślne.
W naszym przypadku elementami były liczby, o wartości domyślnej 0,
ale dla obiektów użyty byłby konstruktor domyślny. Zatem powinien
on wtedy istnieć! Następnie dodajemy liczbę 10.5 na początek listy.
Pozostałe 5 elementów przesuwa się w prawo: po tej operacji lista
ma już sześć elementów. W czwartej linii na nowej początkowej
pozycji (a więc przed wstawione tam przed chwilą 10.5) wstawiamy dwie
liczby z tablicy
tab: liczby z pozycji 1 i 2, czyli liczby 4
i 6. Zatem teraz lista ma osiem elementów:
    4 6 10.5 0 0 0 0 0
Dla list (list,
deque, ale nie
vector), prócz
metod operujących na końcu kolekcji, szczególnie efektywnie
zaimplementowane są metody operujące na jej początku. Analogicznie
do metod
push_back,
pop_back
 i 
back,
o których już mówiliśmy, działają metody
push_front,
pop_front
 i 
front.
 W miarę możności należy raczej
korzystać z funkcji operujących na końcach kolekcji, a nie np. z
funkcji
insert
 czy
erase, których implementacja
jest zwykle wolniejsza.
T.R. Werner, 21 lutego 2016; 20:17