Incapsulamento, Getter/Setter e Costruttori
public vs private)Header, compilazione e linking sono la Lezione 8, insieme a stack e heap.
Una classe è il modello (il progetto) per definire un “oggetto”. Contiene: 1. Dati (noti come attributi o data members) 2. Funzioni (note come metodi, member functions o interfaccia)
#include <iostream>
class Neuron {
public: // Accessibile da chiunque!
double weight;
double bias;
};
int main() {
Neuron n1;
1 n1.weight = 1.12;
return 0;
}Gli utenti della tua classe devono fare affidamento esclusivamente sull’interfaccia pubblica (i metodi), senza mai manipolare o dipendere dai dettagli interni. Questo concetto è il cuore dell’Incapsulamento.
private: I dati (le variabili) dovrebbero quasi sempre essere privati. Nessuno dall’esterno può leggerli o scriverli.public: I metodi che definiscono cosa un oggetto “sa fare” devono essere pubblici.Regola d’oro
Se permetti agli utenti di accedere direttamente ai dati, quando vorrai aggiornare o modificare il funzionamento interno della classe, “romperai” il codice di tutti i tuoi utenti!
Ecco cosa succede se permetti accesso pubblico. Tu, autore della classe, decidi di migliorare il tuo codice e cambi il nome di una variabile da weight a peso_sinaptico.
La tua Classe Aggiornata:
Il Codice del Cliente:
int main() {
Neuron n1;
1 // ERRORE DI COMPILAZIONE!
n1.weight = 1.12;
return 0;
}Usa metodi pubblici (Helper/Utility) per accedere ai dati privati.
La tua Classe:
_ per distinguerla.
I Setters non servono solo a mascherare i nomi. Permettono di convalidare i dati!
In C++, nel momento in cui crei un oggetto, il compilatore invoca istantaneamente una funzione speciale chiamata Costruttore. - Deve avere lo stesso identico nome della classe. - Non ha nessun tipo di ritorno (nemmeno void!). - Serve a inizializzare i dati in uno stato valido, evitando “spazzatura” di memoria.
class Datum {
private:
double value_;
public:
1 Datum() {
value_ = 0.0;
}
};Datum d1; nel main(), questa funzione parte da sola e imposta value_ a 0.
Proprio come le normali funzioni, i costruttori possono accettare parametri (Overloading).
Datum d1;.
Datum d2(5.5);.
Cosa succede se creiamo un oggetto assegnandogliene subito un altro uguale?
int main() {
Datum d1(5.5);
1 Datum d2 = d1;
return 0;
}private da d1 a d2.
(Attenzione: questa “copia gratuita” diventerà pericolosissima quando useremo la memoria dinamica! Ne parleremo nella prossima lezione).
Se non scrivete nessun costruttore, il compilatore ve ne regala uno di default. Sui tipi base (double, int, i puntatori) non fa nulla: i dati restano non inizializzati, con valori spazzatura.
MA ATTENZIONE:
class Datum {
public:
Datum(double v) { value_ = v; } // Ho scritto SOLO il parametrizzato!
private:
double value_;
};
int main() {
1 // ERRORE DI COMPILAZIONE!
Datum d1;
return 0;
}Datum() { ... }.
Possiamo mettere un costruttore in private? Certo, ed è il modo per imporre che un oggetto esista una volta sola — un generatore di numeri casuali, per dire: due generatori con lo stesso seme danno la stessa sequenza.
Generatore g;.
static vuol dire due cose diverse — metodo che non appartiene a un oggetto, variabile che nasce una volta sola — e sono materia della Lezione 13: per ora è una ricetta.
Note
Il randnumgen.hpp che vi daremo fa la stessa cosa più alla svelta, con un unico rng globale. Il Singleton serve quando volete anche impedire il secondo.
Due minuti, senza portatile. Compilate mentalmente con -std=c++17.
1. Quante righe escono, e quali?
2. La classe Persona della slide sui setter non ha costruttori.
1. Quattro righe: default, param, copia, param. L’ultima riga è la sorpresa: D d = D(2.0); sembra costruire un temporaneo e poi copiarlo, e fino al C++14 faceva esattamente questo — cinque righe. Dal C++17 l’eliminazione della copia in questo caso non è un’ottimizzazione concessa al compilatore, è obbligatoria: il temporaneo non esiste proprio, d viene costruito sul posto. Provate a rimettere -std=c++11 -fno-elide-constructors e la quinta riga ricompare.
2. Non si sa. setEta(200) rifiuta il valore — il setter fa il suo mestiere — ma eta_ non è mai stato inizializzato da nessuno, quindi getEta() legge spazzatura: è undefined behaviour, può stampare qualunque cosa. Ed è esattamente il motivo per cui la slide dopo parla di costruttori: validare gli ingressi non serve a niente se l’oggetto nasce già in uno stato invalido. Incapsulamento e costruttori sono due metà dello stesso problema.