Classi, Struct e Incapsulamento
Comprendi la differenza tra struct e class, gli specificatori di accesso, l'incapsulamento, le funzioni membro const, i membri statici e l'inizializzazione moderna degli aggregati.
Cos'è una Classe?
Una classe in C++ è un tipo definito dall'utente che raggruppa dati (variabili membro) e operazioni su quei dati (funzioni membro) in un'unica unità. Questo raggruppamento si chiama incapsulamento — uno dei quattro pilastri dell'OOP.
L'idea chiave è che una classe definisce un invariante: un insieme di regole che devono essere sempre vere riguardo al suo stato interno. Per esempio, una classe Date potrebbe richiedere che il mese sia sempre compreso tra 1 e 12. L'incapsulamento ti permette di far rispettare questi invarianti controllando l'accesso allo stato interno.
struct vs class
In C++, struct e class sono quasi identici. L'unica differenza è lo specificatore di accesso predefinito: struct usa public come impostazione predefinita, mentre class usa private. Convenzione: usa struct per semplici aggregati di dati, class quando hai invarianti da mantenere.
#include <iostream>
#include <string>
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class BankAccount {
std::string owner; // private
double balance; // private
public:
BankAccount(const std::string& name, double initial)
: owner{name}, balance{initial} {}
double get_balance() const { return balance; }
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};
int main() {
Point p{3.0, 4.0}; // aggregate initialization
std::cout << p.x << ", " << p.y << '\n';
BankAccount acct{"Alice", 1000.0};
acct.deposit(500.0);
std::cout << acct.get_balance() << '\n'; // 1500
// acct.balance = -999; // ERROR: private member
return 0;
}Specificatori di Accesso e il Puntatore this
Il C++ offre tre livelli di accesso: public (chiunque può accedere), private (solo la classe e i suoi friend) e protected (la classe, i suoi friend e le classi derivate). Il puntatore this è un puntatore implicito all'oggetto corrente, disponibile all'interno di ogni funzione membro non statica.
#include <iostream>
#include <string>
class Employee {
private:
std::string name_;
int id_;
protected:
double salary_; // accessible to derived classes
public:
Employee(const std::string& name, int id, double salary)
: name_{name}, id_{id}, salary_{salary} {}
// const member function: promises not to modify the object
const std::string& get_name() const { return name_; }
int get_id() const { return id_; }
// 'this' pointer usage — return *this for method chaining
Employee& set_name(const std::string& name) {
this->name_ = name; // 'this->' disambiguates
return *this;
}
Employee& set_id(int id) {
this->id_ = id;
return *this;
}
void print() const {
std::cout << "Employee{" << name_ << ", #" << id_
<< ", $" << salary_ << "}\n";
}
};
int main() {
Employee e{"Bob", 42, 75000.0};
e.set_name("Robert").set_id(43); // method chaining via *this
e.print(); // Employee{Robert, #43, $75000}
return 0;
}Membri Statici e friend
I membri statici appartengono alla classe, non a una singola istanza. Usali per stato condiviso o funzioni di utilità. La parola chiave friend concede a una funzione non membro o a un'altra classe l'accesso ai membri privati — usala con parsimonia, perché rompe l'incapsulamento.
#include <iostream>
class Widget {
int value_;
// C++17: inline static — define in header, no .cpp needed
inline static int count_ = 0;
public:
explicit Widget(int v) : value_{v} { ++count_; }
~Widget() { --count_; }
static int get_count() { return count_; }
// friend function can access private members
friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};
std::ostream& operator<<(std::ostream& os, const Widget& w) {
return os << "Widget(" << w.value_ << ')'; // accesses private value_
}
int main() {
Widget a{10}, b{20};
std::cout << Widget::get_count() << '\n'; // 2
std::cout << a << '\n'; // Widget(10)
{
Widget c{30};
std::cout << Widget::get_count() << '\n'; // 3
}
std::cout << Widget::get_count() << '\n'; // 2
return 0;
}Inizializzazione di Aggregati e Inizializzatori Designati (C++20)
Un aggregato è una classe o una struct senza costruttori dichiarati dall'utente, senza membri dati non statici privati o protetti, senza funzioni virtuali e senza classi base virtuali. Il C++20 aggiunge gli inizializzatori designati, che ti permettono di indicare per nome quali membri stai inizializzando.
#include <iostream>
#include <string>
struct Config {
std::string host = "localhost";
int port = 8080;
bool use_tls = false;
int max_connections = 100;
};
int main() {
// Traditional aggregate initialization
Config c1{"example.com", 443, true, 200};
// C++20 designated initializers — much more readable
Config c2{
.host = "api.example.com",
.port = 443,
.use_tls = true
// .max_connections uses default value 100
};
std::cout << c2.host << ':' << c2.port << '\n';
std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
std::cout << "Max: " << c2.max_connections << '\n'; // 100
return 0;
}Rendi i membri dati privati per impostazione predefinita ed esponi solo ciò che serve attraverso un'interfaccia pubblica minima. Se una classe non ha invarianti da mantenere (è solo un contenitore di dati), usa invece una struct con membri pubblici. Non aggiungere getter e setter per ogni campo — così vanifichi lo scopo dell'incapsulamento. Fornisci invece operazioni significative come deposit() e withdraw() anziché set_balance().
Se una funzione membro non modifica lo stato dell'oggetto, marcala come const. Senza const non puoi chiamare la funzione su un oggetto const o attraverso un riferimento const — cosa che succede continuamente quando passi gli oggetti per const&. Un const mancante su un accessore è una fonte comune di errori di compilazione nelle basi di codice più grandi.
structeclassdifferiscono solo per l'accesso predefinito:structè public,classè private- L'incapsulamento protegge gli invarianti di classe — usa dati
privatecon un'interfaccia pubblica significativa - Marca come
constle funzioni membro che non modificano l'oggetto, così funzionano con i riferimenticonst - Usa
inline static(C++17) per i membri statici definiti negli header - Gli inizializzatori designati del C++20 rendono l'inizializzazione degli aggregati leggibile e auto-documentante
friendva usato con parsimonia — tipicamente per gli overload di operatori che necessitano dell'accesso privato
Quiz — Metti alla prova le tue conoscenze
(15 XP)1. Qual è l'UNICA differenza tra `struct` e `class` in C++?
2. Perché dovresti marcare come `const` le funzioni membro di accesso?
3. Cosa ottieni con `inline static int count_ = 0;` in C++17?