C++ Moderno
Le feature fondamentali di C++11, C++14, C++17 e C++20 che ogni programmatore deve conoscere.
Storia degli Standard C++
C++98/03
Primo standard. STL, template base, eccezioni, RTTI.
C++11 π
Rivoluzione. auto, lambda, move semantics, smart pointers, thread.
C++14
Raffinamento. Lambda generiche, make_unique, binary literals.
C++17 β
Structured bindings, if constexpr, std::optional, std::variant, filesystem.
C++20
Concepts, Ranges, Coroutines, modules, std::format.
C++23
std::expected, std::print, more ranges.
g++ -std=c++17 -Wall file.cpp1. auto e Deduzione dei Tipi (C++11)
#include <iostream>
#include <vector>
#include <map>
#include <typeinfo>
using namespace std;
int main() {
// auto deduce il tipo dal valore iniziale
auto x = 42; // int
auto y = 3.14; // double
auto z = 3.14f; // float
auto s = "ciao"s; // std::string (con suffisso s, C++14)
auto b = true; // bool
cout << typeid(x).name() << "\n"; // i (int)
// Molto utile per tipi complessi
map<string, vector<int>> mappa;
mappa["a"] = {1, 2, 3};
// Senza auto:
// map<string, vector<int>>::iterator it = mappa.begin();
// Con auto:
auto it = mappa.begin();
// decltype: deduce il tipo di un'espressione
int a=5, b2=10;
decltype(a + b2) risultato = a + b2; // int
cout << risultato << "\n";
// Range-based for loop (C++11) β usa auto
vector<int> v = {1, 2, 3, 4, 5};
for (auto elem : v) cout << elem << " "; // Copia
cout << "\n";
for (auto &elem : v) elem *= 2; // Modifica (reference)
for (const auto &elem : v) cout << elem << " "; // Sola lettura
cout << "\n";
// auto con const e reference
const auto &ci = v[0]; // const int&
auto *ptr = &a; // int*
return 0;
}
2. Lambda Expressions (C++11)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
// Sintassi: [capture](params) -> return_type { body }
// Lambda semplice
auto saluta = [](const string &nome) {
cout << "Ciao " << nome << "!\n";
};
saluta("Mario");
// Lambda con valore di ritorno esplicito
auto somma = [](int a, int b) -> int { return a + b; };
cout << somma(3, 4) << "\n";
// Capture by value [=]
int base = 10;
auto aggiungi_base = [base](int x) { return x + base; };
// base viene copiato al momento della creazione della lambda
// Capture by reference [&]
int contatore = 0;
auto incrementa = [&contatore]() { contatore++; };
incrementa(); incrementa(); incrementa();
cout << "Contatore: " << contatore << "\n"; // 3
// Capture tutto: [=] copia tutto, [&] reference a tutto
int a=1, b2=2;
auto tutto_per_valore = [=]() { return a + b2; };
auto tutto_per_ref = [&]() { a++; b2++; };
// Lambda mutabile (puΓ² modificare le copie)
int n = 5;
auto inc = [n]() mutable { n++; return n; };
cout << inc() << "\n"; // 6 (ma n esterno rimane 5)
cout << n << "\n"; // 5
// Uso con algoritmi STL β il caso d'uso piΓΉ comune
vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
// Sort personalizzato
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Decrescente
// Count_if
int pari = count_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
cout << "Numeri pari: " << pari << "\n";
// Transform
vector<int> quadrati(v.size());
transform(v.begin(), v.end(), quadrati.begin(), [](int x) { return x*x; });
// Lambda generica (C++14) β usa auto come parametro
auto massimo = [](auto a, auto b) { return a > b ? a : b; };
cout << massimo(3, 5) << "\n";
cout << massimo(3.14, 2.71) << "\n";
cout << massimo(string("abc"), string("xyz")) << "\n";
// std::function per salvare lambda
function<int(int)> fib = [&fib](int n) -> int {
return n <= 1 ? n : fib(n-1) + fib(n-2);
};
cout << "fib(10) = " << fib(10) << "\n";
return 0;
}
3. Move Semantics (C++11)
Ottimizzazione fondamentale: invece di copiare un oggetto temporaneo, ne prendiamo possesso delle risorse. Elimina copie inutili e rende il codice molto piΓΉ veloce.
#include <iostream>
#include <vector>
#include <utility>
#include <cstring>
using namespace std;
class Buffer {
int *dati;
size_t size;
public:
Buffer(size_t n) : dati(new int[n]), size(n) {
cout << "Costruttore: alloca " << n << " int\n";
}
// Copy constructor: O(n) β copia i dati
Buffer(const Buffer &altro) : dati(new int[altro.size]), size(altro.size) {
memcpy(dati, altro.dati, size * sizeof(int));
cout << "Copy constructor: COPIA " << size << " int\n";
}
// Move constructor: O(1) β prende i puntatori
Buffer(Buffer &&altro) noexcept : dati(altro.dati), size(altro.size) {
altro.dati = nullptr; // Invalida l'originale
altro.size = 0;
cout << "Move constructor: SPOSTA (nessuna copia!)\n";
}
// Move assignment
Buffer &operator=(Buffer &&altro) noexcept {
if (this != &altro) {
delete[] dati;
dati = altro.dati;
size = altro.size;
altro.dati = nullptr;
altro.size = 0;
cout << "Move assignment\n";
}
return *this;
}
~Buffer() {
if (dati) delete[] dati;
}
size_t getSize() const { return size; }
};
Buffer crea_buffer(size_t n) {
Buffer buf(n); // Costruisce
return buf; // Move avviene automaticamente (NRVO/RVO)
}
int main() {
Buffer b1(100); // Costruttore
Buffer b2 = b1; // Copy constructor
Buffer b3 = move(b1); // Move constructor β b1 Γ¨ ora invalido
Buffer b4 = crea_buffer(50); // Move (o RVO: ottimizzato dal compilatore)
cout << "b2.size = " << b2.getSize() << "\n";
cout << "b3.size = " << b3.getSize() << "\n"; // 100
cout << "b1.size = " << b1.getSize() << "\n"; // 0 (spostato!)
// std::move con i container
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = move(v1); // O(1) β sposta il puntatore interno
cout << "v1.size=" << v1.size() << " v2.size=" << v2.size() << "\n";
return 0;
}
4. Structured Bindings e altri C++17
#include <iostream>
#include <map>
#include <tuple>
#include <optional>
#include <variant>
#include <string>
using namespace std;
// ===== Structured Bindings (C++17) =====
tuple<int, string, double> get_info() {
return {42, "Mario", 99.5};
}
// ===== std::optional β valore che potrebbe non esistere =====
optional<int> cerca_elemento(const vector<int> &v, int target) {
for (int i=0; i<(int)v.size(); i++)
if (v[i] == target) return i; // Restituisce l'indice
return nullopt; // Non trovato
}
// ===== std::variant β union type-safe =====
using Numero = variant<int, double, string>;
void stampa_numero(const Numero &n) {
if (holds_alternative<int>(n)) cout << "int: " << get<int>(n);
else if (holds_alternative<double>(n)) cout << "double: " << get<double>(n);
else cout << "string: " << get<string>(n);
cout << "\n";
}
// ===== if constexpr (C++17) β branching a compile time =====
template<typename T>
void stampa_tipo(T val) {
if constexpr (is_integral_v<T>)
cout << "Intero: " << val << "\n";
else if constexpr (is_floating_point_v<T>)
cout << "Decimale: " << val << "\n";
else
cout << "Altro: " << val << "\n";
}
int main() {
// Structured bindings
auto [id, nome, voto] = get_info();
cout << id << " " << nome << " " << voto << "\n";
// Loop su map con structured bindings
map<string, int> voti = {{"Mario",30}, {"Anna",28}, {"Luca",25}};
for (const auto &[nome2, v] : voti) {
cout << nome2 << ": " << v << "\n";
}
// std::optional
vector<int> v = {1, 5, 3, 8, 2};
if (auto pos = cerca_elemento(v, 8)) {
cout << "Trovato alla posizione " << *pos << "\n";
} else {
cout << "Non trovato\n";
}
// optional con value_or
auto r = cerca_elemento(v, 99);
cout << "Posizione (o -1): " << r.value_or(-1) << "\n";
// std::variant
Numero n1 = 42;
Numero n2 = 3.14;
Numero n3 = string("hello");
stampa_numero(n1);
stampa_numero(n2);
stampa_numero(n3);
// if constexpr
stampa_tipo(42);
stampa_tipo(3.14);
stampa_tipo(string("test"));
return 0;
}
5. Initializer Lists e Uniform Initialization (C++11)
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;
class Vettore {
vector<double> dati;
public:
// Costruttore con initializer_list
Vettore(initializer_list<double> lista) : dati(lista) {}
double norma() const {
double s = 0;
for (double d : dati) s += d*d;
return sqrt(s);
}
void stampa() const {
cout << "[";
for (size_t i=0; i<dati.size(); i++) {
if (i) cout << ", ";
cout << dati[i];
}
cout << "]";
}
};
struct Punto { int x, y, z; };
int main() {
// Uniform initialization con {}
int a{42}; // Come int a = 42
double b{3.14};
string s{"ciao"};
vector<int> v{1,2,3,4,5};
Punto p{1, 2, 3}; // Aggregate init
// Importante: {} impedisce narrowing conversion!
// int x{3.14}; // ERRORE di compilazione (perdi dati)
// int x = 3.14; // OK (ma warning) β troncamento silenzioso
// nullptr (C++11) β sostituisce NULL
int *ptr = nullptr; // Meglio di: int *ptr = NULL o int *ptr = 0
if (!ptr) cout << "Puntatore nullo\n";
// Initializer list personalizzata
Vettore v1{1.0, 2.0, 3.0};
v1.stampa();
cout << " β norma: " << v1.norma() << "\n";
// Range-based for con initializer_list inline
for (int x : {10, 20, 30, 40, 50}) cout << x << " ";
cout << "\n";
return 0;
}
6. constexpr β Calcoli a Compile Time (C++11/14/17)
#include <iostream>
#include <array>
using namespace std;
// constexpr: calcolata a compile time
constexpr int fattoriale(int n) {
return n <= 1 ? 1 : n * fattoriale(n-1);
}
constexpr int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2); // C++14 permette if in constexpr
}
// Calcola potenze di 2 a compile time
constexpr int potenza2(int n) { return n == 0 ? 1 : 2 * potenza2(n-1); }
// constexpr if (C++17)
template<typename T>
constexpr bool e_intero = is_integral_v<T>;
int main() {
// Calcolato a compile time β nessuna runtime computation!
constexpr int f10 = fattoriale(10);
constexpr int fib15 = fibonacci(15);
constexpr int p10 = potenza2(10);
cout << "10! = " << f10 << "\n"; // 3628800
cout << "fib(15) = " << fib15 << "\n"; // 610
cout << "2^10 = " << p10 << "\n"; // 1024
// Usabile come dimensione di array a compile time
array<int, fattoriale(5)> arr; // array di 120 elementi
cout << "Array size: " << arr.size() << "\n";
// constexpr vs #define
// #define MAX 100 // Nessun tipo, problematico
constexpr int MAX = 100; // Ha tipo, debuggabile, namespace-safe
return 0;
}
7. Highlights di C++20
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <format> // C++20
#include <concepts> // C++20
using namespace std;
// Concepts: vincoli sui template (C++20)
template<typename T>
concept Numerico = is_arithmetic_v<T>;
template<Numerico T>
T somma(T a, T b) { return a + b; }
// Range-based programming (C++20)
void demo_ranges() {
vector<int> v = {5,3,8,1,9,2,7,4,6};
// Filtra e trasforma in modo dichiarativo
auto risultato = v
| views::filter([](int x){ return x % 2 == 0; }) // Pari
| views::transform([](int x){ return x * x; }); // Elevati al quadrato
for (int x : risultato) cout << x << " ";
cout << "\n";
// Prendi i primi 3
auto primi3 = v | views::take(3);
for (int x : primi3) cout << x << " ";
cout << "\n";
}
// std::format (C++20) β come Python f-string
void demo_format() {
string nome = "Mario";
int eta = 30;
double pi = 3.14159;
// Simile a printf ma type-safe
cout << format("Nome: {}, EtΓ : {}\n", nome, eta);
cout << format("Pi: {:.4f}\n", pi);
cout << format("Hex: {:#x}\n", 255);
cout << format("Larghezza: {:>10}\n", "destra");
}
int main() {
// Concepts
cout << somma(3, 4) << "\n"; // OK: int
cout << somma(1.5, 2.7) << "\n"; // OK: double
// somma(string("a"), string("b")); // ERRORE: non soddisfa Numerico
// Ranges
demo_ranges();
// Format (richiedono compilatore recente)
// demo_format();
return 0;
}
Riscrivi questo codice C++03 usando le feature moderne: usa auto per i tipi, range-based for loop, lambda invece dei funtori, e nullptr invece di NULL.
Input: vettore di interi, trova i pari, ordinali, stampali.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {7,2,9,4,1,8,3,6,5};
// Filtra pari con remove_if + lambda
vector<int> pari;
for (auto x : v)
if (x % 2 == 0) pari.push_back(x);
// Ordina con lambda
sort(pari.begin(), pari.end(), [](int a, int b){ return a < b; });
// Stampa
for (const auto &x : pari) cout << x << " ";
cout << "\n";
// Versione ancora piΓΉ moderna con algoritmi STL
copy_if(v.begin(), v.end(), back_inserter(pari), [](int x){ return x%2==0; });
sort(pari.begin(), pari.end());
for_each(pari.begin(), pari.end(), [](int x){ cout << x << " "; });
return 0;
}Implementa una classe Matrix che gestisce una matrice dinamica. Implementa il Rule of Five: distruttore, copy constructor, copy assignment, move constructor, move assignment. Verifica che spostare una matrice sia O(1) mentre copiarla Γ¨ O(nΒ²).
#include <iostream>
#include <cstring>
#include <utility>
using namespace std;
class Matrix {
double *dati;
int righe, colonne;
public:
Matrix(int r, int c) : righe(r), colonne(c) {
dati = new double[r*c]();
cout << "Alloca " << r*c << " elementi\n";
}
double &at(int r, int c) { return dati[r*colonne+c]; }
// 1. Distruttore
~Matrix() { delete[] dati; }
// 2. Copy constructor β O(n*m)
Matrix(const Matrix &m) : righe(m.righe), colonne(m.colonne) {
dati = new double[righe*colonne];
memcpy(dati, m.dati, righe*colonne*sizeof(double));
cout << "Copiata matrice " << righe << "x" << colonne << "\n";
}
// 3. Copy assignment
Matrix &operator=(const Matrix &m) {
if (this != &m) {
delete[] dati;
righe=m.righe; colonne=m.colonne;
dati = new double[righe*colonne];
memcpy(dati, m.dati, righe*colonne*sizeof(double));
}
return *this;
}
// 4. Move constructor β O(1)
Matrix(Matrix &&m) noexcept : dati(m.dati), righe(m.righe), colonne(m.colonne) {
m.dati=nullptr; m.righe=m.colonne=0;
cout << "Spostata matrice (O(1))\n";
}
// 5. Move assignment
Matrix &operator=(Matrix &&m) noexcept {
if(this!=&m){delete[]dati;dati=m.dati;righe=m.righe;colonne=m.colonne;m.dati=nullptr;}
return *this;
}
int size() const { return righe*colonne; }
};
int main() {
Matrix a(100, 100); // 10000 elementi
Matrix b = a; // COPIA β costosa
Matrix c = move(a); // SPOSTA β O(1)
cout << "c.size=" << c.size() << " a.size=" << a.size() << "\n";
return 0;
}Crea una classe Pipeline che permette di costruire una catena di trasformazioni su un vettore usando filter, map, reduce e take con una sintassi fluente: Pipeline(v).filter(...).map(...).take(5).result().
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
using namespace std;
class Pipeline {
vector<int> dati;
public:
Pipeline(vector<int> v) : dati(move(v)) {}
Pipeline filter(function<bool(int)> pred) {
vector<int> res;
copy_if(dati.begin(), dati.end(), back_inserter(res), pred);
return Pipeline(move(res));
}
Pipeline map(function<int(int)> f) {
vector<int> res(dati.size());
transform(dati.begin(), dati.end(), res.begin(), f);
return Pipeline(move(res));
}
Pipeline take(int n) {
vector<int> res(dati.begin(), dati.begin() + min(n, (int)dati.size()));
return Pipeline(move(res));
}
Pipeline sort_asc() {
auto d = dati; sort(d.begin(), d.end()); return Pipeline(move(d));
}
int reduce(function<int(int,int)> f, int init=0) {
return accumulate(dati.begin(), dati.end(), init, f);
}
vector<int> result() const { return dati; }
void print() const {
for (int x : dati) cout << x << " "; cout << "\n";
}
};
int main() {
vector<int> v = {5,3,8,1,9,2,7,4,6,10,11,12};
Pipeline(v)
.filter([](int x){ return x%2==0; }) // Solo pari
.map([](int x){ return x*x; }) // Eleva al quadrato
.sort_asc() // Ordina
.take(4) // Prendi i primi 4
.print();
// Output: 4 16 36 64 100 144 β prende i 4 piΓΉ piccoli
int somma = Pipeline(v)
.filter([](int x){ return x>5; })
.reduce([](int acc, int x){ return acc+x; });
cout << "Somma elementi > 5: " << somma << "\n";
return 0;
}