La Standard Template Library (STL) in C++
La Standard Template Library, comunemente abbreviata in STL, è il cuore della libreria standard del C++. Si tratta di un insieme di contenitori, iteratori e algoritmi generici, costruiti sui template, che offrono strutture dati efficienti e operazioni di uso comune già pronte e collaudate. Conoscere la STL significa evitare di reinventare la ruota e scrivere codice più conciso, sicuro ed efficiente. I suoi tre componenti fondamentali lavorano insieme in modo elegante.
I contenitori
I contenitori sono strutture dati che memorizzano collezioni di oggetti. Quello più usato è std::vector, un array dinamico ad accesso rapido. Accanto ad esso, la STL offre numerosi altri contenitori, ciascuno con caratteristiche prestazionali diverse a seconda dello scenario d'uso. Tra i più comuni vi sono std::map, che associa chiavi a valori mantenendo le chiavi ordinate, e std::set, che conserva un insieme di elementi unici e ordinati.
#include <map>
#include <string>
#include <iostream>
int main() {
// Associa nomi a eta
std::map<std::string, int> ages;
ages["Mario"] = 30;
ages["Giulia"] = 28;
// Itera sulle coppie chiave-valore
for (const auto& entry : ages) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
return 0;
}
In una std::map ogni elemento è una coppia composta da una chiave, accessibile con first, e un valore, accessibile con second. L'accesso tramite parentesi quadre inserisce automaticamente la chiave se non è già presente.
Gli iteratori
Gli iteratori sono il meccanismo che permette di scorrere gli elementi di un contenitore in modo uniforme, indipendentemente dalla sua struttura interna. Si comportano in modo simile ai puntatori: si dereferenziano per accedere al valore e si incrementano per avanzare. Ogni contenitore fornisce un iteratore al primo elemento, restituito dal metodo begin, e un iteratore che segue l'ultimo, restituito dal metodo end, usato come sentinella di fine.
#include <vector>
#include <iostream>
int main() {
std::vector<int> numbers = {10, 20, 30};
// Scorre il contenitore tramite iteratori
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << std::endl;
}
return 0;
}
Nella pratica quotidiana, il ciclo basato su intervallo che abbiamo già incontrato usa internamente proprio gli iteratori, offrendo una sintassi più leggibile. Conoscere gli iteratori espliciti resta però importante, perché sono il linguaggio comune con cui i contenitori dialogano con gli algoritmi.
Gli algoritmi
L'intestazione algorithm raccoglie decine di funzioni generiche che operano su intervalli definiti da una coppia di iteratori. Anziché scrivere a mano cicli per ordinare, cercare, contare o trasformare elementi, si invocano questi algoritmi collaudati. Vediamo come ordinare un vettore e cercarvi un elemento.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> values = {40, 10, 30, 20};
// Ordina in modo crescente
std::sort(values.begin(), values.end());
// Cerca il valore 30
auto found = std::find(values.begin(), values.end(), 30);
if (found != values.end()) {
std::cout << "Valore trovato" << std::endl;
}
return 0;
}
Espressioni lambda negli algoritmi
Molti algoritmi accettano un criterio personalizzato sotto forma di funzione. Il modo più comodo per fornirlo è l'espressione lambda, una funzione anonima definita direttamente nel punto in cui serve. Una lambda si scrive con una coppia di parentesi quadre, dette lista di cattura, seguite dai parametri e dal corpo. Nell'esempio seguente si conta quanti elementi soddisfano una condizione.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
// Conta i numeri pari usando una lambda come criterio
auto evenCount = std::count_if(numbers.begin(), numbers.end(),
[](int n) { return n % 2 == 0; });
std::cout << "Numeri pari: " << evenCount << std::endl;
return 0;
}
La filosofia della STL
La forza della STL sta nella separazione tra contenitori e algoritmi, resa possibile dagli iteratori che fanno da ponte tra i due mondi. Un algoritmo come std::sort non sa nulla del contenitore su cui opera: gli bastano gli iteratori che ne delimitano l'intervallo. Questa progettazione consente di combinare un numero ridotto di algoritmi con molti contenitori, ottenendo una enorme varietà di operazioni a partire da pochi componenti riutilizzabili.
Con questo articolo si conclude il secondo ciclo della nostra serie. Abbiamo costruito un percorso che dall'ereditarietà e dal polimorfismo, passando per la gestione della memoria, gli smart pointer, i template e le eccezioni, ci ha condotti fino alla libreria standard. Da qui in avanti potrai approfondire argomenti come la semantica di spostamento, la concorrenza con i thread e le funzionalità introdotte dagli standard più recenti, per padroneggiare il C++ in tutta la sua ricchezza.