Membri e Metodi Statici
static
static
friend
Fino ad ora, abbiamo imparato che il C++ si basa sull’Incapsulamento: ogni oggetto è una scatola chiusa con i propri dati. Se definiamo una classe Particella con un attributo massa, ogni singola particella creata avrà il suo personale valore di massa.
class Particella {
public:
double massa_;
};
int main() {
Particella p1; p1.massa_ = 5.0;
1 Particella p2; p2.massa_ = 10.0;
}p1 e p2 sono entità separate in RAM. Modificare p2 non ha alcun effetto su p1.
Questa indipendenza è il punto di forza della programmazione orientata agli oggetti (OOP). Le istanze sono “isole” che non interferiscono tra loro.
Ma cosa succede se abbiamo un’informazione che riguarda tutti gli oggetti contemporaneamente?
Immaginate di voler tenere traccia di quante istanze di Particella sono state create nel corso del vostro programma.
Un attributo di classe come int totale_particelle; non funzionerebbe! Essendo un normale attributo, ogni particella avrebbe il suo contatore personale (partente da 0), che non sa nulla delle altre particelle.
I programmatori alle prime armi (che spesso provengono dal C standard) tendono a risolvere questo problema dichiarando una variabile globale fuori da tutto.
1int TOTALE_PARTICELLE = 0;
class Particella {
public:
Particella() {
TOTALE_PARTICELLE++; // Incrementiamo la variabile globale
}
~Particella() {
TOTALE_PARTICELLE--; // Decrementiamo alla distruzione
}
};Usare variabili globali in C++ (e in qualsiasi linguaggio moderno) è considerata una pratica disastrosa.
Warning
TOTALE_PARTICELLE = -999; e distruggere la logica della vostra applicazione.Vogliamo una variabile globale, sì, ma protetta e confinata all’interno della nostra classe.
staticIl C++ introduce la parola chiave static per i membri di una classe.
Se dichiari un attributo come static, stai dicendo al compilatore: “Non creare una copia di questa variabile per ogni oggetto. Creane una sola in assoluto, condivisa da tutte le istanze di questa classe!”.
class Particella {
private:
// Questa è CONDIVISA da tutte le particelle.
1 static int totale_particelle_;
// Questa è UNICA per ogni particella.
double massa_;
};private, nessuno fuori dalla classe può manomettere il contatore!
static: Dichiarazione (.hpp)Le variabili static richiedono una cura particolare: devono essere separate tra file Header e file Source.
Nel file .hpp (l’Interfaccia) ci limitiamo a dichiarare che la variabile esisterà.
Particella.hpp
static: Inizializzazione (.cpp)Nel file .cpp (l’Implementazione), la variabile statica deve essere inizializzata. E deve essere fatto fuori da qualsiasi costruttore! Questo perché la variabile statica esiste ancor prima che il primo oggetto venga creato!
Particella.cpp
#include "Particella.hpp"
// INIZIALIZZAZIONE DELLA VARIABILE STATICA
// Si fa usando il nome della classe e l'operatore di scope ::
// Nota: NON devi rimettere la keyword "static" qui!
1int Particella::totale_particelle_ = 0;
Particella::Particella() {
2 totale_particelle_++;
}
Particella::~Particella() {
3 totale_particelle_--;
}main().
static: Un esempio completoRivediamo cosa accade in RAM se eseguiamo questo codice:
int main() {
// totale_particelle_ in memoria vale 0
Particella p1; // totale_particelle_ diventa 1
Particella p2; // totale_particelle_ diventa 2
{ // Apro uno scope limitato
Particella p3; // totale_particelle_ diventa 3
} // p3 muore qui! totale_particelle_ diventa 2
// totale_particelle_ vale 2!
return 0;
}È un meccanismo perfetto per tenere traccia di ID, contatori, risorse condivise (come la connessione a un Database o un File di Log).
Tutto fantastico. Abbiamo incapsulato la nostra variabile statica mettendola private.
Ma ora… come facciamo a leggerla? Dovremo scrivere un Getter!
class Particella {
public:
1 int getTotale() const { return totale_particelle_; }
};Ma aspetta! Se non ho creato nemmeno un oggetto, come faccio a leggere il totale (che dovrebbe essere 0)? Non ho un oggetto su cui chiamare getTotale()!
static (Le funzioni di Classe)Per risolvere questo impasse, il C++ ci permette di dichiarare static anche le Funzioni Membro (i metodi).
Un metodo static non appartiene ai singoli oggetti, appartiene all’Intera Classe. Può essere invocato anche se non esistono istanze.
class Particella {
private:
static int totale_particelle_;
public:
// Metodo STATICO!
1 static int getTotale() { return totale_particelle_; }
};const alla fine. Approfondiremo nella prossima slide il perché.
staticPer invocare un metodo statico nel main, non usiamo il punto . su un oggetto. Usiamo l’operatore di scope :: direttamente sul nome della classe!
int main() {
// Non ho creato nessuna particella!
// Invocazione statica
1 std::cout << "Totale: " << Particella::getTotale();
// Ora creo un oggetto
Particella p1;
// L'output ora sarà 1
std::cout << "Totale: " << Particella::getTotale();
return 0;
}staticI metodi static vivono nel “vuoto cosmico” dell’astrazione della classe. Non sono agganciati a un oggetto fisico. Pertanto:
Important
this: Non esiste il puntatore this in un metodo statico, perché non c’è nessun “oggetto corrente”.massa_). Quale massa dovrebbe leggere, se non c’è l’oggetto?const finale: Come visto prima, il const alla fine di un metodo serve a proteggere l’oggetto fisico da modifiche. Non essendoci l’oggetto fisico, mettere const non ha senso (è un errore di compilazione).I metodi statici possono accedere SOLO a variabili statiche!
In ambiti scientifici e di Machine Learning, i metodi statici non si usano solo per i contatori. Si usano per creare le cosiddette Utility Classes.
Immagina di dover applicare la funzione di attivazione Sigmoide a un neurone. La formula è universale: \(f(x) = \frac{1}{1 + e^{-x}}\)
Dovremmo per forza creare un oggetto Attivazione per usarla?
Non ha senso. La sigmoide è logica matematica pura, non ha uno “stato”.
Raggruppiamo tutte le funzioni matematiche in una Classe che contiene SOLO metodi statici!
#include <cmath>
class MathUtils {
public:
static double relu(double x) {
return (x > 0) ? x : 0.0;
}
static double sigmoid(double x) {
return 1.0 / (1.0 + std::exp(-x));
}
};
int main() {
// Uso le funzioni come fossero una libreria matematica!
1 double y = MathUtils::relu(-5.0);
}