Distruttori, RAII e Metodi Costanti
~Classe)Come gestiamo i delete dentro agli oggetti? Se il Costruttore prepara l’oggetto alla nascita, il Distruttore fa le pulizie al momento della morte!
~.In che ordine muoiono gli oggetti allocati nello Stack? Nell’ordine esattamente inverso a quello in cui sono stati creati! (Last In, First Out).
class Elemento {
public:
int id_;
Elemento(int i) { id_ = i; std::cout << "Nato " << id_ << "\n"; }
~Elemento() { std::cout << "Morto " << id_ << "\n"; }
};
int main() {
Elemento a(1);
Elemento b(2);
Elemento c(3);
1 return 0;
}Morto 3 Morto 2 Morto 1
Il Distruttore è il posto naturale in cui posizionare i delete per le variabili allocate internamente all’oggetto. Questo garantisce che l’oggetto non generi Memory Leak quando muore!
MyLayer.hpp
Quello che abbiamo appena visto è uno dei design pattern più importanti del mondo C++. Si chiama RAII (Resource Acquisition Is Initialization).
L’idea è semplice ma geniale: - L’acquisizione di una Risorsa (Memoria Heap, un File, un Lock) deve avvenire nel Costruttore. - Il rilascio della risorsa deve avvenire sempre e solo nel Distruttore.
In questo modo, siccome il C++ garantisce che i distruttori vengano chiamati sempre e infallibilmente alla distruzione dell’oggetto, è impossibile scordarsi di liberare le risorse!
Ecco un esempio senza memoria heap, ma con un file fisico!
Logger esce dallo scope, il S.O. chiuderà il file in modo pulito e sicuro!
const: L’Incapsulamento TotaleRipassiamo il principio di minor privilegio. Immagina un banale metodo Getter per leggere una variabile. Non dovrebbe mai poterla alterare, no?
class Datum {
private:
double value_;
public:
1 double getValue() {
value_ = 999.0; // Oops, ho alterato il dato!
return value_;
}
};const sui metodiAggiungendo la parola chiave const alla fine della firma del metodo, promettiamo al compilatore che questo metodo agirà in “Sola Lettura” per l’intero oggetto!
.cpp.
C’è un secondo motivo per dichiarare i metodi const. Se passi un oggetto tramite Costante per Reference (const Class&), il compilatore ti proibirà di chiamare qualsiasi metodo che non sia marchiato const!
d.
getValue() non fosse marcato const, il compilatore direbbe: “Non so cosa faccia getValue(). Potrebbe alterare d! Per sicurezza, lo blocco”.
Important
Tutti i metodi che non alterano lo stato della classe devono terminare con la keyword const. Altrimenti i tuoi utenti non potranno mai passarli per const reference!
Vediamo le implicazioni della regola aurea su un codice reale:
class Vettore {
private:
double x_, y_;
public:
Vettore(double x, double y) { x_ = x; y_ = y; }
void scalo(double f) { x_ *= f; y_ *= f; } // NON CONST: Modifica l'oggetto
double lunghezza() const { return std::sqrt(x_*x_ + y_*y_); } // CONST: Sola Lettura
};
1void analizzaVettore(const Vettore& v) {
2 // v.scalo(2.0); // ERRORE! v è const, scalo() non lo è.
3 std::cout << v.lunghezza(); // OK! lunghezza() è const.
}v da metodi invasivi.
const.
Gli argomenti di default li conoscete dalla Lezione 5. Ora che abbiamo separato interfaccia e implementazione (Lezione 8) si aggiunge una regola che in una funzione libera non c’era:
.hpp), mai nel .cpp: ripeterlo è un errore di compilazione.class Counter {
public:
// step ha come default 1
1 void increment(int step = 1);
};
// Nel file .cpp (NON rimettere = 1 qui!)
void Counter::increment(int step) {
count_ += step;
}
int main() {
Counter c;
c.increment(); // Usa il default: incrementa di 1
c.increment(5); // Ignora il default: incrementa di 5
}L’abuso degli argomenti di default porta spesso a conflitti insidiosi.
Quando conviene usare gli argomenti di default e quando conviene scrivere una funzione Overloaded (due funzioni con lo stesso nome)?
Meglio il Default: Quando la logica interna è identica, ma varia solo un parametro di “comodità”.
Costruiamo il nostro DynamicArray!
DynamicArray.hpp e DynamicArray.cpp.double.dimensione.
new[] per allocare l’array nello Heap e salvare il puntatore.delete[].imposta(int index, double valore). (Inserisci un check per evitare accessi out-of-bounds!).leggi(int index) const che restituisca il valore. (Nota il const!).(Prova a testarlo dal main in un loop infinito: con il distruttore la RAM resterà stabile, senza il distruttore esploderà!)
.hppDynamicArray.hpp
.cppDynamicArray.cpp
#include "DynamicArray.hpp"
#include <iostream>
DynamicArray::DynamicArray(int size) {
size_ = size;
1 data_ = new double[size_];
std::cout << "Allocati " << size_ << " elementi.\n";
}
DynamicArray::~DynamicArray() {
2 delete[] data_;
std::cout << "Memoria liberata correttamente!\n";
}
void DynamicArray::imposta(int index, double value) {
if (index >= 0 && index < size_) { // Protezione boundaries
data_[index] = value;
}
}
double DynamicArray::leggi(int index) const {
if (index >= 0 && index < size_) return data_[index];
return 0.0;
}