Da WIKI CoderDojo Firenze.

Tutorial C++ con le librerie Qt – Parte IV

A cosa serve un oggetto

Fin dall’inizio del nostro tutorial abbiamo detto che c’erano delle parti del JavaScript che ci sarebbero mancate nel C++, come a esempio gli eventi, e avremmo cercato sostegno in Qt per ritrovarle.

Un’altra privazione che ci potrebbe dare malinconia sarebbe la scomparsa dei pulsanti, delle caselle di testo e delle immagini. Sembra incredibile che queste tramite HTML e JavaScript siano così facili da gestire e invece nel C++, arrivati alla IV parte del nostro discorso, non se ne sia vista neppure l’ombra. Non saremo mica destinati a proseguire per sempre con una finestrella bianca dove compaiono sole poche righe di testo?


No, non siamo destinati, ma non è facile abbandonarla.

Il C++ non è stato pensato per lavorare all’interno di un browser né per interagire in modo immediato con degli oggetti grafici. Di solito non ci facciamo caso, ma nel nostro computer, anche in questo momento, sono in azione decine di piccoli programmi che noi non vediamo. A seconda del sistema operativo si possono chiamare daemons o services, ma il nome non cambia la sostanza: sono programmi che compiono operazioni che non hanno bisogno di interagire con l’utente.

Se non ci avete mai pensato, eccovi qualche esempio: perché quando spostate il mouse il puntatore vi segue sullo schermo? È ovvio che c’è un programma in ascolto che aspetta di ricevere dei segnali dal mouse e, nel momento in cui li riceve, li decifra e adotta dei comportamenti di conseguenza. Lo stesso discorso pari pari si potrebbe fare per la tastiera o per la scheda di rete, come per mille altre cose.

Il computer è una macchina complessa piena di sensori e il sistema operativo è composto da centinaia di programmi che si prendono cura di tutti queste problematiche. Noi in realtà abbiamo la possibilità di interagire con un numero molto ristretto di programmi, che spesso sono conosciuti come applicativi. Non è detto quindi che a un linguaggio di programmazione sia richiesto di saper gestire con facilità interfacce grafiche come finestre, pulsanti, caselle di testo e quant’altro; anzi, moltissimi linguaggi molto apprezzati vengono usati per scrivere programmi che agiscono sullo sfondo, invisibili all’utente.


Il C++ è un buon compromesso fra questi due ‘mondi’: non nasce per gestire interfacce grafiche, ma può riuscirci con grande efficienza, a patto di perderci un po’ di tempo. Noi stiamo per compiere i primi passi in quella direzione perché, se è vero che il concetto di oggetto in informatica è piuttosto ampio e forse soggetto a diverse interpretazioni, possiamo intanto metterci d’accordo nel dire che finestre, caselle di testo e pulsanti sono degli oggetti.

In realtà abbiamo già incontrato oggetti di tipo diverso: vi ricordate che abbiamo detto che una string è un oggetto? Quindi gli oggetti non sono solo oggetti grafici, ma il vantaggio di parlare degli oggetti grafici è che è molto più facile descrivere le loro caratteristiche.


Allora cominciamo a descriverli.

Una delle caratteristiche più apprezzate di un oggetto è che di solito ha dei metodi. Abbiamo già detto che nel C++ i metodi non sono altro che funzioni e si scrivono esattamente come tutte le funzioni. La loro caratteristica è che si accompagnano a un determinato oggetto e interagiscono solo con quell’oggetto lì, mentre non possiamo far fare loro altre cose.

Uscendo dal C++ e parlando in generale, possiamo immaginarci un metodo come un’azione che un oggetto è in grado di compiere. Facciamo l’esempio di un pulsante. Mettiamo il caso che ci faccia comodo avere un pulsante che, quando riceve il click del mouse, fa comparire una finestra di avviso (a esempio, “Click ricevuto!”).

Nel JavaScript sappiamo già farlo.

In un file HTML scriviamo:

<input type="button" value="Fai click per ricevere un avviso" onclick="mostraAvviso()" />

e in un file JavaScript collegato:

function mostraAvviso() {
     alert ("Click ricevuto!");
 }

Per un problema così semplice non abbiamo in apparenza nemmeno bisogno di creare un oggetto.

Ma in realtà non è così: il fatto è che nello HTML ci sono un sacco di oggetti già pronti all’uso e <input type="button"… è uno di quelli.

Il JavaScript a sua volta è in grado di interagire con questi oggetti e ‘integrarli’ aggiungendoci dei metodi, ossia delle azioni che vengono compiute quando l’utente interagisce con l’oggetto.


L’esempio di un pulsante grafico è molto immediato e cercheremo quanto prima di replicarlo nel C++, anche se ci vorrà un po’. Per ora cerchiamo di capire questa comodità di un oggetto e del perché siano così ‘di moda’, ossia cosa sia un metodo e perché piaccia così tanto.

Collegando le funzioni che scriviamo a un determinato oggetto noi siamo in grado di scrivere un codice molto ordinato e comprensibile composto da oggetti che possono fare determinate cose. A seconda di ciò che vogliamo fare, potremo poi usare un oggetto o un altro.

Per dire, se vogliamo gestire del testo, possiamo usare un oggetto string, che ci offre metodi per modificarlo, ad esempio operandoci dentro delle sostituzioni, come abbiamo già fatto. Se invece vogliamo gestire un gruppo di dati simili fra di loro possiamo usare un vector, che ci offre degli strumenti come l’indice.

La prima caratteristica di un oggetto è quindi che può compiere delle azioni. Queste azioni sono programmate nei metodi, che altro non sono che funzioni con un nome diverso.


Adesso cerchiamo di capire cos’è una proprietà di un oggetto.

Lo avevamo già anticipato parlando di length: una proprietà non è altro che una variabile (ok, può essere anche qualcos’altro, ma ne parleremo molto più avanti).

Anche in questo caso stiamo parlando di una variabile che è collegata a un determinato oggetto e non ha senso se non in relazione a esso.


Cerchiamo di cogliere l’utilità di questa caratteristica degli oggetti.

Abbiamo detto un sacco di volte che il C++ è pensato perché un sacco di persone possano lavorare sullo stesso programma senza pestarsi i calli. Però poi abbiamo trovato solo due tipi di variabili: quelle globali, che tutti possono modificare, e quelle locali, che muoiono appena si esce dalla funzione.

Detta così, parrebbe un mondo molto in bianco e nero in cui si può scegliere solo fra due possibilità: o tutto o niente. O una variabile visibile dappertutto che non muore mai, o una variabile nascostissima nella nostra funzione che ci abbandonerà quanto prima. Un po’ drastico, no?

È vero che esistono i parametri, i puntatori e i riferimenti, ma l’apparenza è di una grande rigidità.


Non farebbe comodo una situazione intermedia? Non so, una variabile che sopravviva alle funzioni, ma non possa essere modificata con facilità da tutti?

Beh, in effetti ci sono altre possibilità come l’uso della parola magica new, ma è ancora un po’ presto per affrontare questo argomento.

Però vediamo se gli oggetti ci possono venire in aiuto. Se dichiaro una variabile come proprietà di un oggetto, quale sarà la vita di quella variabile?

A questa domanda possiamo già dare una risposta, pur non avendo ancora visto come sono fatti gli oggetti, perché è piuttosto semplice: esattamente la stessa. Se un oggetto è definito in una funzione, muore alla fine della funzione. Se è definito fuori da ogni funzione, sopravvive fino alla fine del programma, ma può essere visto da ogni parte del codice. Le sue proprietà vivono e muoiono con lui.


A prima vista, quindi, non ci è di nessun aiuto, però un oggetto può avere un’altra caratteristica: una variabile può essere nascosta al suo interno ed essere inaccessibile al resto del codice. In pratica diventa una proprietà nascosta dell’oggetto.

Qual è l’utilità? L’utilità è nelle parole “al resto del codice”. Significa che quella variabile continua a rimanere accessibile a quelle funzioni che fanno parte dell’oggetto, ossia ai metodi dell’oggetto.


In pratica posso creare una variabile e nasconderla dentro un oggetto. Mettiamo che l’oggetto sia globale: tutto il codice può vedere l’oggetto, ma la variabile rimane comunque inaccessibile perché è nascosta lì dentro.

Poniamo che io abbia bisogno di modificare i dati contenuti in quella variabile. Bene, questo può diventare possibile se scrivo una funzione che faccia parte dell’oggetto, ossia sia un metodo dell’oggetto, che modifica quei dati come voglio io.

La cosa può sembrare cervellotica, ma ecco la conclusione: questo può capitare solo se io posso accedere al codice dell’oggetto!

In altre parole: se ho a disposizione il file dove è scritto il codice dell’oggetto, allora posso aggiungere un metodo che mi fa interagire con quella variabile, ossia mi fa modificare i dati contenuti.

Se invece non ho a disposizione quel file, ergo: il codice l’ha scritto qualcun altro, allora io non ho modo di andare a rompergli le uova nel paniere; potrò usare quell’oggetto, ma senza accedere ai dati di quella proprietà.

E lo stesso vale per gli altri: se scrivo un oggetto io e lo dichiaro globale, tutti potranno usarlo. Ma se ho dichiarato nascosta (si dice privata) una delle variabili lì contenute, nessuno potrà accedere ai dati di quella proprietà. La cosa sarà a mia discrezione: se scrivo un metodo che consenta di leggere quei dati, essi potranno essere letti, ma nient’altro; se scrivo un metodo che consenta di modificarli, essi potranno essere modificati. Ma potranno essere modificati come decido io. A esempio, se si trattasse di una variabile int, potrei decidere di consentire solo di incrementare il valore della variabile, ma mai di diminuirlo.

Potrei addirittura consentire di modificare i dati continuando a impedire che vengano letti.


Ho quindi ottenuto ciò che stavo cercando: una variabile globale che non sia facilmente modificabile.


Adesso cercheremo di immaginare e poi progettare un oggetto che abbia qualcuna di queste caratteristiche, ossia abbia delle proprietà, dei metodi e che alcune di queste proprietà siano private, ossia siano inaccessibili dall’esterno e si possano modificare solo tramite dei metodi pensati ad hoc.


Un robottino in JavaScript

Facciamo finta di essere in vacanza su qualche spiaggia tropicale e lì, non avendo nulla da fare, al sole di mezzodì ci punga vaghezza di costruire un piccolo robot. Purtroppo siamo lontani da casa e, rovistando fra i bagagli, vengono fuori solo pinne, boccagli e costumi da bagno: un po’ poco per costruire un robot. A questo punto siamo costretti a fare buon viso a cattivo gioco e a rimandare il nostro progetto.

Intanto, però, possiamo fantasticare su cosa il nostro piccolo robot dovrebbe saper fare. A questo punto, per passare il tempo ci mettiamo a simulare quel robot sullo schermo del nostro pc per decidere come dovrà funzionare. Non potendolo vedere, ci immaginiamo di avere un telecomando per farlo andare a destra e a sinistra, un sensore che ci dice in che posizione si trova e un indicatore dello stato della batteria.


A questo punto siamo pronti a costruire il nostro primo oggetto: un robottino virtuale, che non si vedrà, ma di cui potremo avere sullo schermo del computer alcune informazioni.

Le azioni che il robottino immaginario potrà compiere saranno andare a destra, a sinistra, in avanti o all’indietro. Queste azioni saranno rappresentate dai metodi.

Il nome del robottino e la sua posizione saranno invece le sue proprietà.


L’interfaccia HTML

Questo tutorial si rivolge a chi ha digerito almeno le basi del JavaScript, ma in questo caso il codice che andremo a scrivere sarà un po’ più complicato di quelli che abbiamo buttato giù fino a qui, perciò forse è meglio se proseguiamo passo passo.

Intanto costruiamo la nostra interfaccia HTML. Per cominciare, vogliamo poter dare un nome al nostro robottino, perciò ci serve una casella di testo in cui scriverlo e un pulsante per dire al JavaScript di venirlo a leggere.

<!DOCTYPE html>
 <html lang="it">
   <head>
     <meta charset="utf-8">

     <title>Proprietà e metodi privati</title>

     <link rel="stylesheet" href="standard.css" />
     <script type="text/javascript" src="privati.js"></script>
   </head>

   <body>
     <div>
       <header>
         <h1>Robottino immaginario</h1>
       </header>

       <div>
         <label>Dai un nome al robottino: </label>
         <input id="battesimo" type="text" />
         <input type="button" value="battezza" onclick="battezza();" />
       </div>

       <footer>
         <p>© Copyright  by CoderDojo Firenze</p>
       </footer>
     </div>
   </body>
 </html>

Un buon nome per il file precedente può essere “index.html”. Quel che segue è il primo embrione del file “privati.js”:

function battezza () {
     var nome = document.getElementById("battesimo").value;
     alert(nome);
 }

Beh, la nostra interfaccia verso il robottino è davvero rudimentale, ma non abbiamo fretta.

Adesso ci vogliamo aggiungere qualche ‘sensore’ e i pulsanti per guidare il robottino.

Aggiungiamo quindi una casella di testo dove ci verrà confermato il nome che abbiamo scelto per il robot. Sotto ne metteremo un’altra dove potremo leggere la posizione del nostro robottino.

Scendendo ancora, dovremo posizionare ben quattro pulsanti, con le scritte “sali”, “scendi”, “sinistra” e “destra”, che ci serviranno per ‘guidare’ il nostro robottino.

Ancora sotto una barra che ci indicherà lo stato di carica del robottino.


Per riuscire a tenere in ordine tutti questi pulsanti e caselle dovremmo spendere un sacco di energia con i CSS, perciò violeremo un po’ i sacri principi dello HTML e useremo una tabella, che è un sistema semplice, ma disapprovato, per piazzare le cose in uno schema preciso.

Ecco il file completo:

<!DOCTYPE html>
 <html lang="it">
   <head>
     <meta charset="utf-8">

     <title>Proprietà e metodi privati</title>

     <link rel="stylesheet" href="standard.css" />
     <script type="text/javascript" src="privati.js"></script>
   </head>

   <body>
     <div>
       <header>
         <h1>Membri privati</h1>
       </header>

       <div>
         <div>
           <label>Dai un nome al robottino: </label>
           <input id="battesimo" type="text" />
           <input type="button" value="battezza" onclick="battezza();" />
         </div>
         <table class="stacco_2em">
           <tr>
             <th colspan="3" class="bordato">Robottino</th>
           </tr>
           <tr>
             <td>nome</td>
             <td colspan="2" class="bordato" id="nome"> </td>
           </tr>
           <tr>
             <td>posizione</td>
             <td colspan="2" class="bordato" id="pos"> </td>
           </tr>
           <tr>
             <td> </td>
             <td> </td>
             <td> </td>
           </tr>
           <tr>
             <td> </td>
             <td class="centrato"><input type="button" value="sali" onclick="vaiSu();" /></td>
             <td> </td>
           </tr>
           <tr>
             <td class="centrato"><input type="button" value="sinistra" onclick="vaiSx();" /></td>
             <td> </td>
             <td class="centrato"><input type="button" value="destra" onclick="vaiDx();" /></td>
           </tr>
           <tr>
             <td> </td>
             <td class="centrato"><input type="button" value="scendi" onclick="vaiGiu();" /></td>
             <td> </td>
           </tr>
           <tr>
             <td> </td>
             <td> </td>
             <td> </td>
           </tr>
           <tr>
             <td>Carica</td>
             <td colspan="2"><progress id="carica" max="100"></progress></td>
           </tr>
         </table>
       </div>

       <footer>
         <p>© Copyright  by CoderDojo Firenze</p>
       </footer>
     </div>
   </body>
 </html>

Un sacco di roba, vero?

Se però non fate caso a tutti i <td> e <tr> della tabella, in realtà ci trovate solo le cose che avevamo elencato prima:

  1. i primi due li avevamo già inseriti:
    1. una casella di testo dove scrivere il nome con cui vogliamo chiamare il robottino;
    2. un pulsante per farlo leggere al JavaScript;
  2. poi:
    1. una casella di testo dove verrà riportato il nome del robottino;
    2. una casella dove comparirà la sua posizione;
    3. quattro pulsanti con le scritte: “sali”, “scendi”, “sinistra” e “destra”
    4. una barra che indica la carica residua.

Ogni pulsante chiama una differente funzione sul file JavaScript, per cui non ci resta che scriverle.


Però… calma! Noi non vogliamo scrivere tante funzioni separate. Vogliamo che siano i metodi di un oggetto ‘robottino’! Sarà possibile farlo?

A questo punto dobbiamo capire come fa il JavaScript a creare un oggetto... su misura.


Il JavaScript è molto flessibile, così tanto che di tecniche per creare oggetti ne sono previste più d’una. Noi sceglieremo quella che ha una qualche somiglianza con il C++: la tecnica della funzione prototipo.

In pratica per progettare un oggetto con questa tecnica basta scrivere una funzione che contenga tutte le variabili e le funzioni che ci servono: le prime diventeranno le proprietà e le seconde i metodi.

La differenza maggiore con il C++ è che il JavaScript non prevede un sistema immediato per rendere una variabile inaccessibile dall’esterno. Per ottenere questo risultato dovremmo introdurre il concetto di closure… Secondo me però non conviene mettere troppa carne al fuoco, perciò facciamo così: prima progettiamo un oggetto che abbia solo proprietà e nessun metodo (sarebbe a dire solo variabili e nessuna funzione). Le funzioni che interagiscono con quelle proprietà, che quindi per ora non saranno private, nasceranno esterne all’oggetto.

In una seconda fase introdurremo le closure, faremo diventare le funzioni esterne metodi dell’oggetto e renderemo private le proprietà; in questo modo saremo in grado di partire da un codice già conosciuto per introdurne di nuovo.


Ci rimboccheremo le maniche fra un secondo, ma intanto vi incollo qui il codice dei CSS che ci aiuteranno a rendere più comprensibile ciò che stiamo facendo. Il file è previsto che si chiami “standard.css”:

.nascosto {
    display: none;
    visibility: hidden;
 }

 td.bordato {
    border: 1px ridge maroon;
    border-collapse: collapse;
 }

 th, td {
     padding: 0.3em;
 }

 .stacco_1em {
    margin-top: 2em;
 }

 .stacco_2em {
    margin-top: 1em;
 }

 .centrato {
    text-align: center;
    text-indent: 0;
 }


Un robottino senza closure

Visto che stiamo creando un robottino, direi che non sarebbe male chiamare così la funzione che funge da stampo per l’oggetto:

function Robottino () {

Niente male!

Adesso rileggiamo l’elenco precedente per decidere quali dati dovrà gestire. Come minimo sono 3:

  1. nome
  2. posizione
  3. stato della carica

In realtà però la posizione dovrà essere rappresentata da almeno 2 numeri: uno che dice quanto si è spostato a destra o a sinistra e uno che ci dice di quanto è si è allontanato (= è salito) o si è avvicinato (= è sceso). Chiamiamo “x” il numero che indica sinistra o destra; chiamiamo invece “y” il numero che dice se si è allontanato o avvicinato.

Facciamo finta che abbiamo posato il nostro robottino al centro di una stanza. Quel punto lì lo chiamiamo 0. Perciò al momento della partenza il robottino si trova in posizione 0.

Se lo facciamo muovere verso sinistra, allora il numero x deve diminuire.

Se lo facciamo muovere verso destra, allora x deve crescere.

Lo stesso discorso lo applichiamo a y: quando il robot si allontana, y sale, mentre quando torna indietro y cala.


Abbiamo dunque bisogno di ben 4 proprietà:

var nome;
 var carica;
 var x;
 var y;

Molto bene, la nostra funzione sta cominciando a prendere forma. A questo punto andiamo sulle cose pratiche: la rima cosa che ci si aspetta che succeda è che venga scritto il nome nella casella di testo con id="battesimo" e poi venga premuto il pulsante “battezza”, il quale a sua volta invoca la funzione battezza()… che però non esiste ancora!

Allora proviamo a immaginarci anche quella. La funzione dovrà solo recuperare il testo contenuto nella casella di testo ‘battezza’ e inserirlo nella variabile ‘nome’ della funzione Robottino(). L’ostacolo è che in questo momento la variabile ‘nome’ è locale alla funzione Robottino() e non vi si può accedere dall’esterno. Dovremo quindi fare una piccola modifica a Robottino() perché accetti almeno un parametro; già che ci siamo, stabiliamo un principio generale: i nostri robottini nasceranno tutti al centro della stanza con il 100% di carica. A questo punto la prima versione della funzione Robottino() può essere questa:

function Robottino (pa_nome) {
     var nome = pa_nome;
     var carica = 100;
     var x = 0;
     var y = 0;
 }

Mentre la prima versione di battezza() può essere:

function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     Robottino(battesimo);
 }

Fin qui, stiamo scrivendo cose già viste molte volte.

Andiamo avanti. Nella tabella della pagina web c’è una casella dove deve ricomparire il nome del nostro robottino. Anche questo si potrebbe ottenere in maniera molto semplice dall’interno di battezza:

    var nome = document.getElementById("nome");
     nome.innerHTML = battesimo;

Il problema è che siamo diventati un po’ esigenti e questo tipo di codice non ci va più bene; infatti noi abbiamo bisogno di una funzione perché poi faremo in modo che diventi un metodo di Robottino(). Una nuova funzione non sarebbe di per sé in grado di leggere la variabile locale ‘battesimo’ perché essa rimarrà nascosta dentro battezza(). Questa nuova funzione dovrà quindi chiedere a Robottino() quale sia il valore della sua variabile ‘nome’.


Mi rendo conto che in questo momento il discorso paia un po’ teorico, però non è difficile. In sostanza ora ci rifiutiamo di usare la variabile ‘battesimo’ di battezza() perché non abbiamo alcuna garanzia che un domani potremo usarla.

Quindi possiamo anche mantenere la prima riga perché siamo pigroni e ora non abbiamo voglia di scrivere una nuova funzione solo per scrivere il nome del robottino nella apposita casella della tabella; però dovremo almeno modificare la seconda perché diventi una chiamata a una funzione che recuperi il valore della proprietà ‘nome’ di Robottino().

Solo che non è assolutamente facile scriverla senza le closure. Sono quindi costretto a una simulazione: rendiamo ‘nome’ di Robottino() globale in modo che possiamo accedervi da qualsiasi parte del codice.

Con questo trucchetto possiamo scrivere una ‘finta’ funzione getNome() che restituisce il valore della variabile ‘nome’ – quando avremo parlato delle closure, trasformeremo getNome() in una ‘vera’ funzione membro di Robottino(), ossia un metodo di Robottino(). Eccola qua:

function getNome() {
     return nome;
 }

Siamo purtroppo costretti a modificare la nostra funzione Robottino() in modo che le proprietà siano tutte globali. Poi rimetteremo tutto a posto:

function Robottino (pa_nome) {
     nome = pa_nome;
     carica = 100;
     x = 0;
     y = 0;
 }

A questo punto all’interno di battezza() non potremo più usare ‘nome’ come identificativo di variabile. Usiamo ‘htmlNome’:

function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     Robottino(battesimo);

     var htmlNome = document.getElementById("nome");
     htmlNome.innerHTML = getNome();
 }

Il passaggio successivo consiste nell’inserire il valore corretto nella casella id="pos".

Il valore corretto è un testo che dica qualcosa di simile:

x = 0, y = 0

I valori non dovranno rimanere sempre 0, ovviamente: varieranno via via che il robottino si sposterà. Potendo accedere alle variabili ‘x’ e ‘y’, rimane solo da aggiungere il codice per comporre il testo da visualizzare: "x = " + x + ", y = " + y.

Ecco qui la nostra rivoluzionaria funzione:

function scriviPosizione() {
     scriviPos = document.getElementById("pos");
     scriviPos.innerHTML = "x = " + x + ", y = " + y;
 }

Abbiamo poi quattro pulsanti che dovrebbero, in maniera un po’ immaginosa, corrispondere ai quattro tasti di un telecomando che farebbe spostare il robottino. La nuova posizione apparirà sul nostro display virtuale nel campo aggiornato dalla funzione precedente.

In realtà, visto che il robottino non esiste, l’unico effetto che dobbiamo gestire è l’aggiornamento delle variabili ‘x’ e ‘y’.

function vaiSu() {
     y += 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiGiu() {
     y -= 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiDx() {
     x += 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiSx() {
     x -= 1;
     scriviPosizione();
     scriviCarica(5);
 }

Il codice precedente ci costringe a scrivere in maniera un po’ diversa la funzione che fa apparire l’indicatore della carica residua. In pratica si parte dal presupposto che la si invochi perché c’è stata una variazione nella carica, perciò l’entità di tale variazione è stabilita da un parametro.

Si potrebbe quindi arrivare a qualcosa del genere:

function scriviCarica(variazione) {
     var scriviCar = document.getElementById("carica");
     carica -= variazione; 
     scriviCar.value = carica;
 }

Ho scritto due esempi che seguono due logiche diverse per far vedere che la creazione degli oggetti non modifica di per sé come si scrive il codice, ma possiamo continuare a vedere le cose da diverse angolazioni come facevamo prima.

Le funzioni che spostano il robottino immaginario sanno di avere accesso direttamente alle variabili ‘x’ e ‘y’ e le modificano senza farsi problemi; però non toccano la variabile ‘carica’, che sarebbe sempre nel loro spazio di visibilità. Preferiscono passare un parametro alla funzione scriviCarica() perché la modifichi lei.

Se non è chiaro, quel che intendo dire è che le funzioni avrebbero potuto essere scritte anche così:

…
 function vaiSx() {
     x -= 1;
     carica -= 5;
     scriviPosizione();
     scriviCarica();
 }

 function scriviCarica() {
     var scriviCar = document.getElementById("carica");
     scriviCar.value = carica;
 }

e avrebbero funzionato lo stesso.

In realtà nessuna delle due strategie sarebbe la migliore nel momento in cui si intende lavorare con gli oggetti. Anche nel JavaScript avremmo potuto fare di meglio, ma ci lasceremo questo tema per quel brontolone del C++ perché in fondo è lui il protagonista di questo tutorial.


Per i soliti pigroni che sanno fare solo il copia-incolla, la prima versione senza closure del nostro robottino è la seguente:

function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     Robottino(battesimo);

     var htmlNome = document.getElementById("nome");
     htmlNome.innerHTML = getNome();
     scriviPosizione();
     scriviCarica(0);
 }

 function Robottino (pa_nome) {
     nome = pa_nome;
     carica = 100;
     x = 0;
     y = 0;
 }

 function getNome() {
     return nome;
  }

 function scriviPosizione() {
     scriviPos = document.getElementById("pos");
     scriviPos.innerHTML = "x = " + x + ", y = " + y;
 }

 function vaiSu() {
     y += 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiGiu() {
     y -= 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiDx() {
     x += 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function vaiSx() {
     x -= 1;
     scriviPosizione();
     scriviCarica(5);
 }

 function scriviCarica(variazione) {
     var scriviCar = document.getElementById("carica");
     carica -= variazione;
     scriviCar.value = carica;
 }


Introduzione alle closure del JavaScript

Prima di procedere con la lettura, cercate di essere sicuri di aver ben chiaro il codice precedente perché è da quello che partiremo.


Abbiamo detto che il metodo che usa il JS per rendere privati i membri di un oggetto (ossia le proprietà e i metodi) è un po’ complicato e si basa sul meccanismo delle closure. Poiché il JS non è l’oggetto di questo tutorial, ci limiteremo a dare una rapida occhiata senza scendere nei dettagli. Uno degli argomenti che ci spinge a questa scelta è che su questo singolo aspetto (rendere privati i membri di un oggetto) il C++ è più semplice e immediato del JS, pertanto non abbiamo motivi di sviscerare a fondo l’argomento.


Intanto ci serve una definizione di closure. Prendiamo quella che troviamo sul sito http://www.w3schools.com:


A closure is a function having access to the parent scope, even after the parent function has closed.


e cerchiamo di tradurla:


una closure è una funzione che può accedere allo spazio di visibilità del suo genitore, perfino dopo che la funzione genitore è terminata.


Può darsi che qualcuno ancora non sappia che cos’è una funzione genitore, perciò procediamo con calma.

Nel C++ abbiamo visto che è possibile assegnare una funzione a un puntatore. Questo è sensato perché la funzione è un blocco di codice che risiede in memoria, pertanto ha un indirizzo e quell’indirizzo può benissimo essere tenuto di mira da un puntatore.

Abbiamo anche detto più di una volta che la relazione fra references e puntatori è stretta, tant’è che possiamo considerare i riferimenti come pointer sottodimensionati.

Invece ci siamo accorti che il JS cerca di sollevarci dalla maggior parte dei problemi decidendo lui quand’è che deve trattare una variabile come riferimento o no; vi ricordate che quando cercate di copiare un oggetto, in realtà state creando un nuovo riferimento allo stesso oggetto? Se non ve ne ricordate, prima di proseguire tornate al tutorial precedente e rileggete la parte “Differenze fra C++ e JavaScript: i references”.

A questo punto, c’è qualcuno che si sorprende se scopre che il JS decide in maniera autonoma quando trattare una variabile come fosse un puntatore? :-)

Guardate un po’ questo codice:

var sembroUnPuntatore = function (nome) {
     alert ("Ciao, " + nome + "!");
 };

Ci trovate qualcosa di strano?

Magari l’avete già usato un sacco di volte, ma non ci avete mai fatto caso. Quel che stiamo facendo è assegnare una funzione a una variabile, operazione assolutamente lecita nel JS e che somiglia molto a ciò che facciamo nel C++ quando assegniamo un indirizzo di funzione a un puntatore.

Questo tipo di funzioni si definiscono function expressions e sono davvero molto comuni.


Come potete notare, la funzione che andiamo ad assegnare alla variabile ‘sembroUnPuntatore’ non ha nome. Anche questo è molto comune: si chiamano funzioni anonime e si possono invocare tramite la variabile cui sono assegnate. Nel C++ sono state introdotte in modo ufficiale solo nel 2011 e si chiamano funzioni lambda, e in effetti stanno riscuotendo una grande successo.


Attenzione! Questa non è solo una dichiarazione di funzione, ma anche un’espressione, perciò deve essere conclusa da un punto e virgola!


Riassumendo: nel JS è possibile creare funzioni senza nome, dette funzioni anonime, che non presentano nessuna differenza con quelle ‘tradizionali’ se non questa: che non hanno nome.

Le funzioni anonime possono essere assegnate alle variabili, le quali in tal caso sembrano assomigliare moltissimo a quelli che nel C++ si chiamano puntatori a funzione. Le funzioni assegnate alle variabili, dette function expressions, possono essere invocate tramite tali variabili.

Le function expressions devono essere concluse con un punto e virgola.


Se dovessimo trattare le closure in modo esaustivo dovremmo introdurre un sottoinsieme delle funzioni anonime che si chiamano self-invoking function expressions, ma per i nostri fini non è necessario.


Procediamo un altro pezzo verso le closure. Nel codice soprastante abbiamo definito una variabile e le abbiamo assegnato una funzione. Se quella variabile si trova fuori da ogni funzione, sarà una variabile globale. Ma il bello è che la possiamo definire all’interno di un’altra funzione:

function esterna () {

     var saluto = document.getElementById("battesimo").value;

     var sembroUnPuntatore = function (nome) {
         alert ("Ciao, " + nome + "!");
     };

 }

Se incollassimo tale funzione nel nostro codice precedente e aggiungessimo alla funzione battezza() un rigo siffatto:

esterna();

forse qualcuno si potrebbe aspettare che alla pressione del pulsante “battezza” compaia un messaggio di saluto. Non vi resta che provare.

Successo niente? No? Beh, non c’è nulla di strano: infatti noi abbiamo definito una funzione, ma non l’abbiamo mai invocata. Per invocarla bisogna passare dalla variabile a cui è assegnata:

sembroUnPuntatore(saluto);

Per cui ecco il codice completo della nuova funzione:

function esterna () {
     var saluto = document.getElementById("battesimo").value;

     var sembroUnPuntatore = function (nome) {
         alert ("Ciao, " + nome + "!");
     };

     sembroUnPuntatore(saluto);
 }

Come vedete la sintassi di una funzione ‘tradizionale’ non anonima e quella di una function expression non varia gran ché (ma ricordatevi il punto e virgola!).


A questo punto siamo in grado di rispondere alla domanda: cosa sarà mai una funzione genitore?

Dall’esempio appena visto siamo venuti a conoscenza del fatto che nel JS le funzioni possono essere incluse una dentro l’altra, e in tal caso quella contenitrice è detta ‘parent’, ossia genitore, di quella contenuta.

Nel nostro esempio esterna() è la funzione parent della function expression ‘sembroUnPuntatore’.


Da quanto sopra dovrebbe essere già intuibile che, quando parliamo di una closure, stiamo parlando di una funzione che è contenuta in un’altra funzione.

La parte da spiegare è quella che dice “…può accedere allo spazio di visibilità del suo genitore…”.

In realtà il concetto è molto semplice e un esempio lo chiarirà subito; guardate questa nuova versione della funzione esterna():

function esterna () {
     var saluto = document.getElementById("battesimo").value;

     var sembroUnPuntatore = function (nome) {
         var testo = "Ciao, " + nome + "! Il robottino si chiama " + saluto + ".";
         alert (testo);
     };

     sembroUnPuntatore("Teodoro");
 }

Provate a scrivere un nome a vostro piacere nel campo di testo “battezza” (che non sia Teodoro, però!) e guardate un po’ cosa succede.


JS_01.png


Sorpresa! La nostra funzione anonima ha avuto accesso alla variabile ‘saluto’ anche se si troverebbe, a una prima occhiata, fuori del suo spazio di visibilità.

Abbiamo quindi imparato una nuova regola: le funzioni anonime possono ‘vedere’ le stesse variabili che vede la funzione parent, ossia quelle locali alla funzione parent, ivi compresi gli argomenti, e quelle globali.

Attenzione! Questa regola non vale nel C++ dove alle funzioni lambda deve essere data una esplicita autorizzazione per accedere alle variabili della funzione che le contiene.


Questo è il significato dell’espressione “può accedere allo spazio di visibilità del suo genitore”: la funzione anonima contenuta può accedere alle variabili nello spazio di visibilità della funzione contenitore.


Ci può essere d’aiuto questa considerazione?

Sì, se la uniamo all’ultima parte della definizione: “…perfino dopo che la funzione genitore è terminata”.


Saltiamoci dentro a piè pari con un esempio.

Abbiamo definito la variabile ‘sembroUnPuntatore’ come locale, ma basta levarci il var davanti e diventa globale. Facciamolo. Poi spostiamo (NON copiamo) la riga

sembroUnPuntatore("Teodoro");

in battezza(), subito sotto

esterna();

Fatto? Provate a fare il refresh con F5 e guardate un po’ cosa succede quando scegliete un nome e fate click su battezza.


JS_02.png


Riuscite a cogliere le implicazioni?

Con l’istruzione

esterna();

in battezza() noi invochiamo la relativa funzione, ma, come abbiamo visto prima, senza chiedere esplicitamente di dare il via alla funzione anonima attribuita alla variabile ‘sembroUnPuntatore’, essa non viene eseguita.

La successiva riga in battezza(),

sembroUnPuntatore("Teodoro");

invoca quella funzione anonima. La funzione anonima è in grado di accedere al valore della variabile ‘saluto’ che è locale a esterna().

Fino a qui ci eravamo già arrivati. Ma ora ripensate un attimo a a ciò che è appena successo: la funzione anonima è stata eseguita, ma… perché mai avrebbe dovuto?

La funzione parent, esterna(), è una comune funzione ‘tradizionale’. Una volta che è stata terminata e il flusso di istruzioni è tornato in battezza()… beh, quello che abbiamo più volte detto è che il suo ‘ambiente’ viene distrutto: il computer libera la memoria, riutilizzandola per altri scopi, e trasformando tutti in nostri dati, compreso il valore di ‘saluto’, in nient'altro che spazzatura.


Come mai allora la funzione anonima è in grado di leggere il contenuto della variabile ‘saluto’ il cui nome dovrebbe addirittura essere andato perduto?


La risposta è molto semplice: quello che abbiamo detto è tutto vero, solo che lo abbiamo detto per il C++ :-)

Il JavaScript funziona in un’altra maniera e in questo caso la differenza è palpabile: nel JS, quando create una closure, ciò che fate in buona sostanza è ‘congelare’ lo stato della funzione parent, che a quel punto non viene più distrutta. Al termine della sua esecuzione rimane a occupare la memoria, tant’è che è possibile, tramite le funzioni anonime, accedere a tutte le sue variabili, che troveremo nello stato in cui le abbiamo lasciate.


Ecco infine risolto il mistero della definizione delle closure che avevamo trovato, e che forse conviene rileggere per essere sicuri di aver capito:


“una closure è una funzione che può accedere allo spazio di visibilità del suo genitore, perfino dopo che la funzione genitore è terminata”


Credo sia abbastanza intuitivo il motivo del largo uso delle closure nel JS, visto che consentono di creare insiemi di dati che non muoiono mai (o meglio: non muoiono finché la variabile cui sono stati assegnati non muore).

Spero sia abbastanza intuitivo anche ciò che andremo a fare ora: ossia utilizzare le funzioni anonime come metodi per accedere a proprietà private di una funzione: in questa maniera potremo finalmente costruire un oggetto simile a quelli che progetteremo nel C++.

Un robottino con le closure

Cominciamo con il nocciolo della questione, ossia la funzione robottino:

function Robottino (pa_nome) {
     var nome = pa_nome;
     var carica = 100;
     var x = 0;
     var y = 0;
 }

Come vedete siamo tornati a dichiarare tutte le variabili come locali. Quel che dobbiamo fare ora è riportare tutte le funzioni che abbiamo scritto in precedenza in questo alveo, facendole diventare funzioni anonime e assegnandole a delle variabili.

C’è però da introdurre un’altra novità: per un problema ‘tecnico’ che ora non è il caso di affrontare perché ci porterebbe troppo lontano, i nomi di queste variabili conviene siano preceduti dalla parola this.

Mi rendo conto che non è molto carino da parte mia non spiegare il significato di this nel JS, ma ci sono due controindicazioni:

  1. this non è così semplice da usare come a prima vista potrebbe sembrare. Una spiegazione breve darebbe l’illusione di aver capito mentre ci sono diverse trappole in agguato. Penso sia più saggio lasciare che venga spiegata in modo dettagliato in altra occasione.
  2. Anche il C++ ha la sua parola this e il fatto è che i significati delle due non coincidono. Pertanto, dato che nel C++ this è molto meno usata e ora ne possiamo rimandare la trattazione, preferisco scegliere di saltare a piè pari l’argomento.

Tratteremo quindi this come l’espressione using namespace std: qualcosa che ha la sua importanza, ma ora ci basta sapere che va messo senza conoscerne il perché.


La prima funzione di cui ci siamo occupati è stata getNome(). Era così:

function getNome() {
     return nome;
 }

Ricominciamo da lei. Direi per semplicità che il nome della funzione può diventare il nome della variabile preceduto da this: tanto la funzione deve diventare anonima!

this.getNome = function () {
     return nome;
 };

Non dimenticate il punto e virgola! Questa ora è una function expression. Ricordiamoci che questa funzione, essendo incorporata in un’altra, è in grado di accedere alle variabili della funzione parent anche se sono locali (come a esempio ‘nome’).

Visto che stiamo lavorando all’interno di una funzione parent e il codice è molto breve, ci prendiamo una piccola licenza e lo scriviamo tutto su una riga:

this.getNome = function () { return nome; };

Direi che abbiamo reso il codice più facile da leggere.


Bon. Possiamo procedere con la funzione di cui ci eravamo occupati subito dopo, scriviPosizione():

function scriviPosizione() {
     scriviPos = document.getElementById("pos");
     scriviPos.innerHTML = "x = " + x + ", y = " + y;
 }

Anche qui possiamo riciclare il nome della funzione per farlo diventare il nome della variabile; per il resto, il codice rimane identico:

this.scriviPosizione = function () {
     scriviPos = document.getElementById("pos");
     scriviPos.innerHTML = "x = " + x + ", y = " + y; 
 };

Proseguiamo a grandi passi con le funzioni successive

function vaiSu() {
     y += 1;
     scriviPosizione();
     scriviCarica(5);
 }

Anche questa rimane identica:

 this.vaiSu = function () {
     y += 1;
     this.scriviPosizione();
     this.scriviCarica(5);
 };

La stessa logica si può applicare alle altre tre che ‘muovono’ il robottino.

Per finire, ricicliamo anche il codice di scriviCarica():

function scriviCarica(variazione) {
     var scriviCar = document.getElementById("carica");
     carica -= variazione; 
     scriviCar.value = carica;
 }

Che rimane:

this.scriviCarica = function (variazione) {
     var scriviCarica = document.getElementById("carica");
     carica -= variazione; 
     scriviCarica.value = carica;
 };

Adesso dobbiamo fronteggiare il cambiamento più vistoso, quello della funzione battezza(). La versione originaria recitava così:

function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     Robottino(battesimo);

     var htmlNome = document.getElementById("nome");
     htmlNome.innerHTML = getNome();
     scriviPosizione();
     scriviCarica(0);
 }

Le prime due righe possono anche rimanere inalterate, ma le modifiche che abbiamo portato alla funzione Robottino() l’hanno fatta diventare qualcosa di un po’ diverso, ossia una funzione di un tipo che di solito si definisce object constructor function, o anche solo constructor function.

Questo tipo di funzioni servono a descrivere i dettagli di un oggetto, che sono in primo luogo le sue proprietà e i suoi metodi. Questa struttura, una volta descritta, non basta a creare da sola un oggetto, ma, come tutte le funzioni, per assolvere al suo compito deve essere invocata.

Ci dobbiamo rendere conto però che non la invocheremo una volta sola: se abbiamo capito come funzionano le closure, ci dovrebbe essere chiaro che una volta invocata, questa funzione non morirà più. Rimarrà in qualche modo congelata, a nostra disposizione, conservando con sé il valore di tutte le proprietà contenute.

Tutte le volte che useremo uno dei suoi metodi, accederemo sempre allo stesso spazio di memoria, quello della stessa funzione, che non è mai stata distrutta.


Adesso ragioniamoci un po’ su.

Se quello che abbiamo detto fosse vero, allora vorrebbe dire che non potremmo mai avere più di un robottino per volta che si aggira per la nostra stanza!

Cerchiamo di immaginarci la sequenza: noi invochiamo la funzione. Il computer la carica in memoria. Lì lei rimane fino alla fine del programma.

Se la invocassimo una seconda volta, cosa succederebbe visto che non è mai ‘morta’? Avremmo un messaggio di errore? Si bloccherebbe il programma?


Niente di tutto questo: l’interprete JS sarebbe in grado di soddisfare la nostra richiesta, ma pagando un prezzo. Infatti libererebbe un nuovo spazio di memoria dove caricare la nostra nuova constructor function, ricreando tutte le proprietà, e dandoci accesso alla nuova funzione. Tutti i dati della vecchia sarebbero per noi resi inaccessibili.


Conclusione: potremmo avere un solo robottino alla volta.


Sarebbe un grosso limite, non vi sembra? Per fortuna il JS offre una soluzione al problema, e anche piuttosto semplice: possiamo assegnare la nostra constructor function a una variabile né più né meno come abbiamo fatto con le funzioni anonime.

Questo ci consente di invocare la funzione quante volte vogliamo, assegnandola ogni volta a variabili diverse. Abbiamo già visto che alle funzioni assegnate alle variabili si può accedere tramite il nome di variabile; grazie a questo meccanismo, saremo in grado di invocare i suoi metodi.

In questo modo potremo creare quanti robottini vogliamo, facendo sì che il computer li riconosca grazie al nome della variabile.


La sintassi per ‘collegare’ una constructor function a una variabile è un po’ diversa da quella tradizionale e fa uso della parola chiave new:

var miaVariabile = new constructorFunction (argomenti);

Credo che la parola new sia molto d’aiuto in questo caso, anziché rappresentare una difficoltà, perché enfatizza il fatto che stiamo creando un nuovo esemplare del nostro oggetto, che si può aggiungere a quelli esistenti, se ce ne sono.

Per new faremo però come per this: la prenderemo per buona così com’è senza approfondirla e in buona misura per lo stesso motivo: anche nel C++ esiste new, ma ha il suo significato, che non è identico a questo. Quindi ce ne occuperemo lì, ma non qui.

Nel nostro caso, visto che vogliamo rendere la nostra variabile globale, non useremo l’istruzione var.

Supponendo di voler chiamare la variabile rbtnn e tenendo di conto che la constructor function è denominata Robottino(), la dichiarazione verrà:

rbtnn = new Robottino(battesimo);

Che è la novità più grande della nuova funzione battezza(). Il resto lo possiamo commentare dopo averlo scritto:

function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     var nome = document.getElementById("nome");

     // Senza 'var' diventa globale
     rbtnn = new Robottino(battesimo);

     nome.innerHTML = rbtnn.getNome();
     rbtnn.scriviPosizione();
 }

Possiamo finalmente prendere coscienza di ciò a cui serve l’istruzione punto ‘.’ nel JS: serve per accedere ai metodi di un oggetto. Si può dire che ‘rbtnn’ sia il nome del nostro oggetto – anche se, per l’appunto, contiene una variabile che si chiama ‘nome’ e questo può far fare confusione.

Il fatto che contenga una proprietà denominata ‘nome’ è una pura coincidenza; avrebbe potuto non esserci.

Per il compilatore, il nome dalla variabile cui è stata assegnata la constructor function è il nome dell’oggetto. Se volessimo al rigo successivo potremmo scrivere:

robottino2 = new Robottino("Mario");

e creeremmo un nuovo oggetto denominato ‘robottino2’.

Qualora scrivessimo:

robottino2.scriviPosizione();

il computer non farebbe confusione: andrebbe a eseguire il metodo scriviPosizione() contenuto nello spazio di memoria assegnato a ‘robottino2’.


La conclusione qual è?

Nel JS ci sono diversi modi per creare un oggetto, ma quello che abbiamo scelto noi richiedere due passaggi:

  1. prima si scrive una funzione, detta prototipo (prototype) dell’oggetto, ma nota anche come funzione costruttore (constructor function); questa funzione contiene tutti i metodi e le proprietà dell’oggetto.
  2. In un secondo momento, si invoca la funzione quante volte si vuole, assegnandola ogni volta ad una variabile diversa; in questo modo creiamo quante copie vogliamo del nostro oggetto, a cui potremo accedere tramite le variabili utilizzate.

Il primo passo consiste quindi nel creare una descrizione del nostro oggetto, una sorta di modello o stampo. In seguito useremo questo stampo per creare copie dell’oggetto.


Concludiamo dando un’occhiata a quella che a una prima occhiata potrebbe sembrare una controindicazioni di questo metodo, e invece rappresenta il suo punto di forza.


A questo punto non abbiamo più una via diretta per collegare a funzione vaiSu() richiesta dal codice HTML con il metodo vaiSu del nostro robottino.

Infatti per arrivare a tale metodo dobbiamo conoscere il nome della variabile cui è stata assegnata la constructor function Robottino(). In altre parole, una volta ci potremmo trovare a invocare:

rbtnn.vaiSu();

e una volta:

robottino2.vaiSu();

Perciò, cosa scriveremo nel nostro file HTML?

Beh, lo HTML non nasce per avere la flessibilità del JS, pertanto non possiamo sperare che ci offra soluzioni altrettanto semplici; quindi è nel JS che dobbiamo cercare.


A dire il vero la domanda nel nostro programma è molto scolastica perché all’atto pratico tutto il codice è pensato per interagire con un solo robottino. Si tratta quindi di un puro esercizio ‘di stile’ e ci serve solo per dare un’occhiata a un tipo di codice che si usa tantissimo, ma le prime volte può sembrare un’inutile perdita di tempo.


La soluzione al nostro problema consiste nel nascondere la chiamata alla funzione ‘vera’ all’interno di una funzione di comodo:

function vaiSu () {
     rbtnn.vaiSu();
 }

Chi pensa che sarebbe meglio evitare questo passaggio e cercare un modo per chiamare direttamente il metodo partendo dallo HTML commette un errore concettuale.

Molti si potrebbero domandare se gli oggetti servono davvero a qualcosa e tutto questo tempo perso a cercare di rendere private delle proprietà abbia un senso. Abbiamo già dato una prima risposta all’inizio del tutorial e potremmo aggiungere che quello di programmare a oggetti oggi è in ‘must’: tutti i linguaggi stanno cercando di spostarsi verso la programmazione a oggetti.


Se quindi vogliamo usare gli oggetti, non possiamo considerare una perdita di tempo il fatto che ci costringano a una certa sintassi, spesso più prolissa di quella della programmazione ‘tradizionale’.


Il codice completo del file privati.js è ora questo:

function Robottino (pa_nome) {
  var nome = pa_nome;
  var carica = 100;
  var x = 0;
  var y = 0;

     this.getNome = function () { return nome; };

     this.scriviPosizione = function () {
      var scriviPos = document.getElementById("pos");
      scriviPos.innerHTML = "x = " + x + ", y = " + y; 
  };

     this.scriviCarica = function (variazione) {
      var scriviCarica = document.getElementById("carica");
      carica -= variazione; 
      scriviCarica.value = carica;
  };

     this.vaiSu = function () { y += 1; this.scriviPosizione(); this.scriviCarica(5); };
  this.vaiGiu = function () { y -= 1; this.scriviPosizione(); this.scriviCarica(5); };
  this.vaiDx = function () { x += 1; this.scriviPosizione(); this.scriviCarica(5); };
  this.vaiSx = function () { x -= 1; this.scriviPosizione(); this.scriviCarica(5); };
 }

 function battezza () {
  var battesimo = document.getElementById("battesimo").value; 
  var nome = document.getElementById("nome");

     // Senza 'var' diventa globale
  rbtnn = new Robottino(battesimo);

     nome.innerHTML = rbtnn.getNome();
  rbtnn.scriviPosizione();
 }

 function vaiSu () {
  rbtnn.vaiSu();
 }

 function vaiGiu () {
  rbtnn.vaiGiu();
 }

 function vaiDx () {
  rbtnn.vaiDx();
 }

 function vaiSx () {
  rbtnn.vaiSx();
 }

Conclusioni, miglioramenti ed esercizi

Ci sarebbero mille altre cose da dire, ma come sempre abbiamo usato il JS come apripista, perciò ci siamo limitati a sottolineare gli aspetti che ci facevano comodo per arrivare al C++.

Questi aspetti sono:

  • un oggetto è in buona sostanza un blocco di codice che include proprietà e metodi; nel JS questo blocco di codice è una funzione, mentre nel C++ non è così;
  • questo blocco di codice ha un nome, ma è solo il nome dello ‘stampo’: gli oggetti che poi saranno creati avranno ognuno il proprio nome;
  • una funzione anonima può essere contenuta dentro un’altra funzione;
  • un oggetto non deve per forza avere dei membri privati, ma la possibilità di rendere qualcosa inaccessibile dall’esterno è uno dei suoi principali vantaggi.

Prima di lasciare il JS, invito a fare un po’ di esperimenti in proprio per entrare più dentro i concetti trovati. Si potrebbe ad esempio provare a aggiungere una funzione che consenta di cambiare il nome al robottino.

Si immagini ad esempio di aggiungere il seguente blocco di codice al file HTML, subito sotto l’istruzione </table>:

        <div class="stacco_2em">
           <label>Rinomina il robottino: </label>
           <input id="rinomina" type="text" />
           <input type="button" value="rinomina" onclick="rinomina();" />
         </div>

Che codice si potrebbe scrivere per gestire questa aggiunta?


Di seguito offro una possibile soluzione, però immersa in una nuova versione di “privati.js”. L’obiettivo è mostrare come si possono affrontare i problemi da punti di vista diversi, ottenendo risultati simili e spesso soddisfacenti.

Il codice che segue presenta delle ‘sviste’ (per esempio, una variabile locale è dichiarata due volte…) e delle forzature. Cercare di individuare gli errori e di capire vantaggi e svantaggi delle diverse versioni del codice può essere un esercizio come un altro.

function Robottino (pa_nome, HTMLpos) {
     var nome = pa_nome;
     var carica = 100;
     var x = 0;
     var y = 0;
     var scriviPos;
     var scriviCarica;

     this.getNome = function () { return nome; };
     this.setNome = function (pa_nome2) { nome = pa_nome2; };

     this.getPosizioneX = function () { return x;    };
     this.getPosizioneY = function () { return y; };

     this.scriviPosizione = function () {
         scriviPos = HTMLpos ? HTMLpos : document.getElementById("pos");
         scriviPos.innerHTML = "x = " + x + ", y = " + y; 
         };

     this.scriviCarica = function (variazione) {
         var scriviCarica = document.getElementById("carica");
         carica -= variazione; 
         scriviCarica.value = carica;
     };

     this.vaiSu = function (passi) { y += passi; this.scriviPosizione(); this.scriviCarica(5); };
     this.vaiGiu = function (passi) { y -= passi; this.scriviPosizione(); this.scriviCarica(5); };
     this.vaiDx = function (passi) { x += passi; this.scriviPosizione(); this.scriviCarica(5); };
     this.vaiSx = function (passi) { x -= passi; this.scriviPosizione(); this.scriviCarica(5); };
 }

 function battezza () {
     var battesimo = document.getElementById("battesimo").value; 
     var nome = document.getElementById("nome");
     var pos = document.getElementById("pos");

     // Senza 'var' diventa globale
     rbtnn = new Robottino(battesimo, pos);

     nome.innerHTML = rbtnn.getNome();
     rbtnn.scriviPosizione();
 }

 function rinomina () {
     var nuovoNome = document.getElementById("rinomina").value;
     var nome = document.getElementById("nome");

     rbtnn.setNome(nuovoNome);
     nome.innerHTML = rbtnn.getNome();
 }

 function vaiSu () {
     rbtnn.vaiSu(1);
 }

 function vaiGiu () {
     rbtnn.vaiGiu(1);
 }

 function vaiDx () {
     rbtnn.vaiDx(1);
 }

 function vaiSx () {
     rbtnn.vaiSx(1);
 }

Nel codice ci sono anche due novità, la riga:

scriviPos = HTMLpos ? HTMLpos : document.getElementById("pos");

e la riga:

rbtnn = new Robottino(battesimo, pos);

Diamoci un’occhiata insieme, partendo dalla seconda.

In questa nuova versione di “privati.js” immaginiamo che si provi ad usare due robottini insieme. Miracolosamente, essi possono ‘nascere’ nello stesso punto (il centro della stanza), ma poi la posizione di uno dei due dovrà essere scritta su un ‘display’ diverso (ossia, un altro campo di testo). Questo nuovo campo di testo potrebbe essere passato al robottino tramite il parametro ‘pos’ al momento della creazione.

Gestire questa novità non sarà facile, ma lo lasciamo come esercizio.


L’altra riga è strettamente connessa a questa: nel caso ci sia un secondo (o un terzo, un quarto…) robottino, la variabile ‘scriviPos’ non dovrà puntare al campo di testo con id = "pos", ma a quello passato tramite l’argomento ‘HTMLpos’.

Come fa il JS a sapere quale campo di testo usare?

L’istruzione sembra difficile, ma leggendola con calma la si può decifrare con una certa facilità.


La sintassi è la seguente:

condizione_vera_o_falsa  ?  conseguenza_se_vero : conseguenza_se_falso

Si tratta di una cosa simile a un’istruzione if, ma sta tutto su una riga.

All’inizio della riga si mette la condizione, anche senza parentesi. Nel nostro caso basta scrivere “HTMLpos”, che sta a significare:

nel caso che la variabile HTMLpos sia vera (nel senso di sia stata definita, abbia un valore)…

Dopo la condizione si mette il punto interrogativo ‘?’, che serve a distinguere la condizione delle sue conseguenze.

Dopo il punto interrogativo, si scrive la prima conseguenza, quella che deve accadere se la condizione è vera. Nel nostro caso, basta riscrivere “HTMLpos”, che in questo caso vuol dire:

(nel caso in cui la variabile HTMLpos abbia un valore) usa, per scriviPos, il valore di HTMLpos

Dopo la prima conseguenza si mette il simbolo di due punti ‘:’. Questo simbolo termina la prima conseguenza e introduce la seconda, quella che si deve realizzare se la condizione è falsa.

Dopo i due punti si trova la conseguenza di una condizione falsa, nel nostro caso “document.getElementById("pos")”. Il suo significato è:

(nel caso in cui la variabile HTMLpos non abbia alcun valore) 
   usa, per scriviPos, il valore restituito dalla funzione document.getElementById("pos")

Da ultimo c’è il buon vecchio punto e virgola.


Un ultimo esercizio che suggerisco (di cui però non sto a proporre alcuna soluzione) consiste nel fare in modo che, esaurita la carica, il robottino si fermi.


Ora però è il momento di lasciare il JavaScript.

Un robottino in C++

Proviamo a costruire il nostro primo oggetto in C++. Cercheremo di ottenere qualcosa di simile a quello che siamo riusciti a mettere insieme con il JavaScript.


Cominciamo con classe

Iniziamo con il creare un nuovo progetto, che chiameremo Robottino e salveremo sempre nella solita cartella. Il progetto sarà sempre un Non-Qt ProjectPlain C++ Project.


Qt_IV_01.png


Qt Creator ci presenta la classica schermata “Hello Word!”, che noi cercheremo di sfruttare a nostro comodo.


Dovete sapere che un sacco di IDE… Vi ricordate cos’è un IDE, vero? Un ambiente integrato di sviluppo, come ad esempio Qt… Beh, un sacco di IDE offrono una funzione apposita per creare oggetti e Qt non fa eccezione. Infatti, se tornate a sbirciare sotto FileNew File or Project, nella finestra che si apre e ormai dovreste trovare familiare, nella colonna di sinistra, trovate la scritta File and Classes.


Gli oggetti nel C++ si creano tramite le class (nel JavaScript abbiamo visto la funzione prototipo, che è la cosa più simile) e alcune delle opzioni che appaiono lì sotto servono proprio per generare lo scheletro di una class tramite una procedura automatizzata, un po’ come Creator fa quando, nel creare un nuovo progetto, ci aggiunge in automatico un file main.cpp e dentro quel file il codice di “Hello Word!”.

Se facciamo click su “C++”, vedremo che le opzioni nella colonna centrale cambiano, offrendoci la possibilità di creare una class.


Qt_IV_08.png


Il motivo che spinge i progettisti di IDE a offrire questo genere di servizi è che di solito le class si scrivono su file separati.

Vi ricordate quando abbiamo preso un file HTML dove c’era dentro di tutto e l’abbiamo suddiviso in tre parti, una per lo HTML, una per il JavaScript e una per i CSS? Beh, in teoria sarebbe giunto il momento di fare lo stesso con il C++: una class non dovrebbe stare insieme a main(), il codice risulterebbe molto disordinato. Per ogni nuovo oggetto che vogliamo progettare dobbiamo scrivere una class e quella class dovrebbe stare in ben due file, nessuno dei quali dovrebbe essere quello in cui c’è main().

Visto quindi che c’è tutto questo lavoro preparatorio da fare per confezionare una class, ossia creare due nuovi file, dar loro dei nomi appropriati, e dopo scrivere il codice della class dividendolo nei due file secondo un certo criterio, ecco che gli IDE ci vengono in soccorso togliendoci una parte del lavoro più noioso.


Però ora noi compiremo un crimine che farà storcere la bocca a più di una persona: scriveremo il codice della nostra class nel file main.cpp, subito sotto main(). La cosa importante in questo momento è capire che cosa stiamo facendo, e soltanto dopo ci porremo la domanda se esisteva un modo un po’ più ‘ordinato’ di farlo.

Visto che ci siamo messi d’accordo così, possiamo far click su Cancel, o Annulla che sia, e tornare al nostro codice: infatti non useremo la procedura automatica ma scriveremo tutto da soli rigo per rigo.


Andiamo subito a confermare un concetto: gli oggetti del C++ si creano tramite le class, che non sono altro che blocchi di codice con un’etichetta, su per giù come le funzioni.

Torniamo a ciò che abbiamo detto in conclusione al codice JS:

  • un oggetto è in buona sostanza un blocco di codice che include proprietà e metodi; nel JS questo blocco di codice è una funzione, mentre nel C++ non è così;

Bene: nel C++ questo blocco di codice si chiama class.


La class è quindi l’equivalente della funzione prototipo o constructor function del JS.

La differenza tra una class e un oggetto è molto concettuale: si dice che un oggetto è una istanza di una class.

Cosa vuol dire? Che la class è un po’ come uno stampo: una volta che lo abbiamo modellato, possiamo usarlo per creare tanti oggetti uguali fra di loro. A ognuno di questi oggetti potremo dare nomi diversi, ma saranno tutti identici uno all’altro perché tutti nati tramite la class che abbiamo scritto.

Fino a qui, non troviamo molte differenze con ciò che abbiamo visto nel JS.

Per ora basta ricordarsi che una class è uno stampo per creare oggetti: gli oggetti nati da una stessa class sono tutti uguali fra di loro.

Se si tiene in mente questo, non dovremmo avere difficoltà a capire perché, per mantenere la promessa di parlare degli oggetti, ci mettiamo a studiare le class.


La parola inglese class viene di solito tradotta con classe. Ancora una volta pare che gli informatici dimostrino una scarsa attitudine all’uso del vocabolario giacché una scelta più accorta sarebbe stata ‘tipo’. Una class infatti, lo abbiamo appena detto, serve per generare oggetti di un certo tipo. Gli oggetti di quel tipo saranno tutti uguali fra di loro.

Viene quasi da sospettare che la parola classe sia stata scelta per la sua assonanza con quella inglese (class) invece che per il concetto che deve veicolare. Tuttavia anche in italiano la parola classe può significare gruppo o categoria, a esempio quando si parla di classe sociale intendendo con questa espressione persone di un certo ‘tipo’, magari contraddistinto da un dato livello di agiatezza. Di conseguenza anche noi ci adegueremo alla consuetudine e parleremo di classi per trattare le classes, consolandoci magari con la considerazione che anche nei CSS è stata usata la stessa approssimazione.


Andiamo quindi a vedere come si può creare lo stampo di un oggetto.

Cerchiamo di ottenere l’equivalente della constructor function Robottino() del JS. Tanto per enfatizzare la similitudine, possiamo chiamare la nostra class con il nome Robot, che è simile, ma non identico, in modo che si riesca sempre a tenere ben distinti nella mente i due codici. Però le class del C++ non sono funzioni, per cui non faremo seguire la parentesi tonda ‘()’ al nome:

class Robot {

Beh, l’inizio non è stato difficile :)

Quindi, una class è un blocco di codice… sappiamo già che questo nel C++ implica una bella coppia di parentesi graffe ‘{}’. In più ha un nome, e anche in questo caso basta scriverlo all’inizio del blocco e il compilatore capisce che è il nome della classe. Non ci vuole molto per arrivare a concludere che tutte le nostre proprietà e i nostri metodi dovranno stare fra le parentesi graffe.

A dire il vero, più avanti scopriremo che è più comodo scriverle da un’altra parte, ma da un punto di vista concettuale l’affermazione è giusta.


Le due novità sono la parola chiave class e la mancanza di parentesi tonde, ma si tratta di due novità annunciate che ribadiscono una distinzione che voglio comunque ripetere: una class non ha nulla a che vedere con le funzioni.

C’è però una similitudine con le function expressions del JS: anche la dichiarazione di una classe finisce con un punto e virgola.


Quindi, andando avanti, possiamo dire che dovremo ottenere un codice del tipo:

class Robot{
    // Qui metteremo proprietà e metodi
 };

L’ho scritto tutto bello appiccicato, come adorano i programmatori in C++.

Proviamo a metterci dentro una proprietà e cerchiamo di scoprire cosa succede. Una proprietà di solito è una variabile, perciò questo codice dovrebbe andare bene:

class Robot{
    int mia_proprieta;
 };

A questo punto abbiamo creato lo ‘stampo’ del nostro primo oggetto nel C++.

Non sarà il più utile degli oggetti concepibili, ma dovrebbe funzionare. A questo punto, però, siamo nella situazione in cui ci siamo trovati nel JS quando abbiamo scritto la nostra constructor function Robottino(): finché non l’abbiamo attribuita a una variabile tramite la parola new, avevamo solo lo stampo, ma ancora nessun oggetto.


Vi sorprenderà scoprire che in questo caso il C++ è più semplice e lineare del JS: infatti non è indispensabile usare la parola new. Vi ricordate quando abbiamo detto che i vector e le string sono oggetti? Eppure come abbiamo fatto a dichiarare una variabile di tipo string?

string testo;

‘testo’ diventa una variabile di tipo string. Infatti, come abbiamo detto poco sopra, la parola class non andrebbe tradotta in modo forzato ‘classe’, ma in modo accurato ‘tipo’. Una class crea un nuovo tipo che si va ad aggiungere a quelli esistenti, come gli int e i long.

Pertanto ecco come creare un esemplare (si dice una istanza) del nostro oggetto Robot:

Robot rbt;

Fine. Non serve altro.

Per accedere alla nostra variabile ‘mia_proprieta’ basterà scrivere:

rbt.mia_proprieta;

O forse no…? :-/

Smettiamola di parlare in teoria e andiamo a guardarci un po’ di codice. Cominciamo con lo scheletro minimo possibile: la funzione main() –che nel C++ non può mancare!– e una classe vuota:


Qt_IV_02.png


E, per i pigroni:

#include <iostream>

 using namespace std;

 int main()
 {
    return 0;
 }

 class Robot{

 };

Questo codice compila senza problemi, ma ha un piccolo difetto: non fa assolutamente nulla. Non è un difetto da poco, ripensandoci.

Forse è meglio metterci qualcosa dentro. In main() potremmo aggiungere:

Robot rbt;

per creare un’istanza del nostro oggetto.

Invece nell’oggetto potremmo inserire una proprietà, ma lasciamo stare quegli antipatici di numeri: mettiamoci una string:

string nome;

Venendo dal JS potremmo pensare che questa proprietà sia accessibile dall’esterno. Infatti non abbiamo fatto nulla per nasconderla, e nemmeno ci siamo preoccupati di scrivere complicate closure per accedervi… Beh, se la pensiamo così, il C++ è pronto a servirci amare delusioni.

Inseriamo in main() la manipolazione minima possibile:

rbt.nome = "Mario";
 cout << "rbt.nome = " << rbt.nome << endl << endl;

Proviamo a compilare il programma completo:

#include <iostream>


using namespace std;


 int main()
 {
    Robot rbt;
    rbt.nome = "Mario";

    cout << "rbt.nome = " << rbt.nome << endl << endl;
    return 0;
 }

 class Robot{
    string nome;
 };

e ci troviamo davanti la faccia accigliata del nostro compilatore:


Qt_IV_03.png


</pre>

Simpatici quei messaggi di errore, vero? Andiamo a darci un’occhiata.

Il primo recita: «In function main(): ‘Robot’ was not declared in this scope».

Intanto traduciamolo, ma con una certa libertà: «Errori contenuti nella funzione main(): la parola 'Robot' non è accessibile all’interno di main()».


Il significato dovrebbe essere chiaro, anche se il motivo forse è misterioso: main() non riesce a vedere il codice class Robot{…};

Gli altri errori sono una diretta conseguenza di questo: non potendo sapere cosa significa 'Robot', main() non capisce nemmeno cosa sia ‘rbt’, che noi vorremmo fosse un’istanza di ‘Robot’.


Qui bisogna tenere presente la differenza fondamentale fra i linguaggi come il JS e i linguaggi come il C++: il JS è interpretato. Ciò significa che il codice viene caricato in memoria, ‘elaborato’ e poi eseguito. Il C++ invece è compilato: questo significa che il compilatore legge il file rigo per rigo per trasformarlo in linguaggio macchina. Nel punto in cui ha incontrato la nostra istruzione “Robot rbt”, ancora non aveva letto la definizione di Robot, pertanto non era in grado di sapere cosa volevamo.

La cosa più semplice, quindi, è spostare la definizione della classe prima di main():

#include <iostream>

 using namespace std;

 class Robot{
    string nome;
 };

 int main()
 {
    Robot rbt;
    rbt.nome = "Mario";

    cout << "rbt.nome = " << rbt.nome << endl << endl;
    return 0;
 }

Non ci resta che andare a vedere che disastro abbiamo combinato ora :-)


Violazione della privacy

Qt_IV_04.png


Sempre peggio! :-)

Quattro messaggi di errore invece che tre.

Ok, ci possiamo rilassare: in realtà il compilatore ci sta segnalando due volte la stessa cosa: abbiamo usato due volte la proprietà ‘nome’ di Robot, una volta al rigo 12 e una volta al rigo 14, che sono rispettivamente:

12:   rbt.nome = "Mario";
 14:   cout << "rbt.nome = " << rbt.nome << endl << endl;

Se non abbiamo voglia di contare, ci possiamo fidare di Qt, che gentilmente ci evidenzia anche con dei pallini rossi le righe dove secondo lui abbiamo sbagliato qualcosa:


Qt_IV_05.png


Il fatto è, ci sta dicendo il compilatore, che «nome is private within this context». Anche se può essere un po’ intricato decifrare la frase intera, possiamo però cogliere l’essenza: ‘nome’ è ‘private’, pertanto non può essere liberamente utilizzata in main().


Sorpresi? Non tanto forse. Il C++ nasce per essere un linguaggio a oggetti, il ché significa per garantire che si possa lavorare in tanti allo stesso codice senza interferire l’uno con l’altro. Pertanto parte dal presupposto che si voglia dichiarare le proprietà private; se non è ciò che desideriamo, dobbiamo specificarlo in modo esplicito, ma anche in questo caso non c’è nessuna difficoltà: basta scrivere ‘public:’:

class Robot{
 public:
    string nome;
 };

Provare per credere. Il codice compila senza errori e dà il risultato atteso:


Qt_IV_06.png


Tutto si può dire fuorché il C++ non abbia un valido supporto per la programmazione a oggetti!


La parola ‘public’ funziona con la logica “da qui in avanti”, nel senso che crea un punto di inizio: da quel momento in poi tutte le variabili dichiarate all’interno della class saranno accessibili dall’esterno. Questo significa che quelle dichiarate prima non lo saranno.

Ad esempio:

class unaClasse{
    int intero_privato;
    long intero_lungo_privato;
    string testo_privato;

 public:
    int intero_pubblico;
    long intero_lungo_pubblico;
    string testo_pubblico;
 };

In questo modo possiamo organizzare il codice secondo le nostre necessità. Anzi, dirò di più: se, dopo aver dichiarato un blocco della nostra classe ‘public’ dovessimo cambiare idea e volere altre variabili inaccessibili dall’esterno, beh, anche in questo caso nessun problema: esiste il corrispondente ‘private’:

class unaClasse{
    int intero_privato;
    long intero_lungo_privato;
    string testo_privato;

 public:
    int intero_pubblico;
    long intero_lungo_pubblico;
    string testo_pubblico;

 private:
    int un_altro_intero_privato;
    long un_altro_intero_lungo_privato;
    string un_altro_testo_privato;
 };

(Per favore, non utilizzate mai dei nomi di variabile così lunghi nei vostri programmi!)

Insomma, nel C++ possiamo fare decisamente come vogliamo e con gran facilità. Non voglio stare qui ora a parlare dell’istruzione ‘protected’, che crea delle variabili private, ma accessibili ad alcune funzioni, però posso introdurvi a un’altra curiosità.


Abbiamo detto che per creare gli oggetti usiamo la parola chiave class che si aspetta che i membri debbano essere privati a meno che li dichiariamo ‘public’; non ci crederete, ma il C++ è così completo che offre anche anche l’alternativa opposta: con struct potete definire degli oggetti del tutto identici a quelli che potete ottenere con class, eccetto che struct si aspetta che i membri siano pubblici, a meno che non li dichiariate ‘private’.

In altre parole:

struct unaStruct{
    int intero_pubblico;
    long intero_lungo_pubblico;
    string testo_pubblico;

 private:
    int intero_privato;
    long intero_lungo_privato;
    string testo_privato;

 public:
    int un_altro_intero_pubblico;
    long un_altro_intero_lungo_pubblico;
    string un_altro_testo_pubblico;
 };

Se non vi basta, vuol dire che siete proprio incontentabili! :-)

Noi comunque struct la useremo pochissimo o per nulla, perché la sintassi ‘tradizionale’ per creare oggetti passa da class. struct è un’eredità del C.


Andiamo un altro po’ avanti.

Perché usare proprietà pubbliche? Rendiamo ‘nome’ privata e poi facciamo in modo che per modificarla si debbano usare funzioni interne a Robot. Questa sarebbe la logica sottostante agli oggetti: fornire delle variabili che durino a lungo, ma non siano ‘facilmente’ modificabili, ossia non siano accessibili da chiunque senza limiti.

Per modificare il contenuto della variabile ‘nome’ ci serve una funzione che accetti un parametro di tipo testo e lo inserisca in ‘nome’. Una cosa del tipo:

void setNome(string battezza)
 {
    nome = battezza;
 }

Questa, come vedete, è una vera e propria funzione, con le sue parentesi tonde e senza punto e virgola alla fine. Mando a capo la parentesi graffa di apertura ‘{‘ perché è lo stile adottato da Stroustrup per le funzioni; un’abitudine, diciamo, ma voi potete fare come vi pare. Ricordiamoci che le funzioni che non restituiscono valori nel C++ devono essere dichiarate void.

All’opposto, se vogliamo conoscere il valore della variabile ‘nome’, abbiamo bisogno di una funzione che restituisca una stringa:

string getNome() 
 {
    return nome;
 }

Per quelli che si fossero già dimenticati tutto dei tutorial precedenti, ricordo che return restituisce una copia del valore della variabile, quindi una copia dei dati contenuti nell’oggetto string. Quindi non stiamo violando la ‘intangibilità’ della variabile ‘nome’, la quale rimane inaccessibile dall’esterno, ma stiamo facendo una copia di ciò che vi è contenuto e stiamo spedendo tale copia.


Giacché abbiamo deciso che ‘nome’ dovrà essere privata, le due funzioni potranno avervi accesso solo se sono contenute nella stessa class:

class Robot{
    string nome;

    void setNome(string battezza)
    {
       nome = battezza;
    }

    string getNome()
    {
       return nome;
    }

 };

Beh, in questo caso, se vogliamo aumentare la leggibilità, possiamo prenderci una licenza e mettere tutto sulla stessa riga:

class Robot{
    string nome;

    void setNome(string battezza) { nome = battezza; }

    string getNome() { return nome; }
 };

Anche main() però dovrà essere diversa, perché non potrà più accedere a ‘nome’. Ecco qui una possibile versione:

int main()
 {
    Robot rbt;
    rbt.setNome("Mario");

    cout << "rbt.nome = " << rbt.getNome() << endl << endl;
    return 0;
 }

Spero di non sembrare presuntuoso a dire che non mi sembra ci sia nulla da spiegare: l’istruzione

rbt.setNome("Mario");

invia il parametro “Mario” all’argomento ‘battezza’ di setNome(), che lo inserisce in ‘nome’.

L’istruzione

rbt.getNome()

recupera il valore di ‘nome’ e lo invia a cout. Il resto dovrebbe essere chiaro.

Proviamo a compilarlo e…


Qt_IV_07.png


PATATRAK!

Un’altra bella distesa di messaggi d’errore :-)


Okay, il C++ è davvero pignolo, lo devo ammettere. Però preferisco procedere così perché dai nostri errori possiamo imparare di più che dalle noiose spiegazioni.

Riuscite a capire dove stia il problema? I messaggi del compilatore sono molto simili a quelli precedenti e i pallini rossi dovrebbero aiutarvi.

Ci siete arrivati?

Ebbene sì, il problema è proprio quello: anche setNome() e getNome() sono privati; non li possiamo usare liberamente in main().


Se la cosa vi lascia sbalestrati, lo capisco: fin’ora avevamo sempre parlato di variabili private, ma mai di funzioni private. Eppure il C++ è fatto così: non dà niente per scontato. Se inserite i vostri metodi in un’area di una class che è dichiarata ‘private’, anche loro saranno protetti dall’esterno.

Se pensate che non ha senso avere una funzione privata, vi sbagliate: vedrete che ne incontreremo; però ammetto che la maggior parte sono pubbliche.


Comunque la soluzione consiste nel tornare ad usare l’istruzione ‘public’:

class Robot{
    string nome;

 public:
    void setNome(string battezza) { nome = battezza; }

    string getNome() { return nome; }
 };

Adesso tutto compila liberamente e il risultato è quello atteso.


Piccola digressione sulle scelte di programmazione

Cerchiamo di aggiungerci le proprietà che abbiamo usato nel JS:

int carica;
int x;
int y;

Gestire questi valori è davvero semplice, ma il nostro problema sarà che al momento non abbiamo a disposizione nulla di equivalente alla comoda interfaccia HTML per dialogare con l’utente, perciò dovremo arrangiarci con il testo. L’effetto sarà molto meno accattivante.

Un buon risultato da ottenere potrebbe essere una scritta riepilogativa del tipo:

Robottino: 'nome robottino'
    posizione: x = 0, y = 0
    carica residua: 100%


Teniamo di conto che non abbiamo una posizione fissa sullo schermo dove far apparire questa scritta, perciò essa si ripeterà tutte le volte che faremo muovere il robottino. Questa sarà la massima interazione che otterremo per ora con l’utente, visto che non vogliamo dedicarci chissà quanto all’interfaccia a caratteri perché con le librerie Qt torneremo ad avere caselle di testo e pulsanti.


Anche per ricevere i comandi dall’utente non abbiamo chissà quali scelte. La cosa più semplice è dargli la possibilità di digitare qualcosa alla tastiera e premere invio. A quel punto ‘decifreremo’ il suo comando e agiremo di conseguenza.

Per ricevere comandi dalla tastiera, forse ve ne ricorderete, è prevista la parola magica cin, che un po’, diciamo, l’opposto di cout: prende i valori in ingresso e li inserisce in una variabile:

string valore_in_ingresso;
 cin >> valore_in_ingresso;

Prima l’avevamo usata per gestire degli int, ma il concetto non cambia. A questo punto possiamo decidere che l’utente può premere ‘a’ per far andare il robottino a sinistra, ‘w’ per farlo andare in alto, ‘s’ per destra e ‘z’ per basso.

Questo può essere gestito da una serie di if:

if ( valore_in_ingresso == “a” ) …
if ( valore_in_ingresso == “w”) …
if ( valore_in_ingresso == “s”) …
if ( valore_in_ingresso == “z”) …


Molti preferirebbero scrivere una sequenza di if- else.

Il blocco di codice che inizia con else è quello che viene eseguito quando la condizione if è falsa. In altre parole:

if (condizione) {
   → se ‘condizione’ è vera, esegue questo codice
}
else {
   → se ‘condizione’ è falsa, non si esegue il codice sopra, ma questo sì!
}


Nel nostro caso avremmo quindi potuto scrivere:

if ( valore_in_ingresso == “a”) {
   …
}
else if ( valore_in_ingresso == “w”) {
   …
}
else if ( valore_in_ingresso == “s”) {
   …
}
else if ( valore_in_ingresso == “z”) {
   …
}

Prima di proseguire, guardate con attenzione i due blocchi di codice e cercate di vedere se ci sono vantaggi o svantaggi.


Beh, il primo non si può definire errato, ma il secondo è più logico.

Immaginate di essere un computer e di dover eseguire il codice. Mettiamo che l’utente abbia digitato “a”. Quando arrivate al primo if, scoprite che la condizione è vera, pertanto eseguite il blocco di codice. Finito il blocco, proseguite con l’istruzione successiva, che è un altro if, che dovete verificare per scoprire che è falso; di conseguenza, saltate alla riga dopo il blocco, che però è ancora un if, che di nuovo dovete valutare come falso…

Insomma, la cosa migliore sarebbe se, una volta eseguito uno degli if, visto che gli altri sono in alternativa, non ne dovreste considerare più nemmeno uno.


Se scrivete il codice come una sequenza di if-else, consentite al computer di comportarsi così (nel caso l’utente digiti una “a”):

  • leggo il primo if; scopro che è vero, perciò eseguo il codice;
  • vado alla riga successiva; scopro che è un else, pertanto, avendo io eseguito il blocco if, so già che la posso saltare, perché una condizione non può essere vera e falsa contemporaneamente!
  • tutti gli if successivi erano contenuti nel blocco else, quindi non li vedo nemmeno – per me è come se non esistessero: vado direttamente alla riga successiva all’ultimo else if.

Se può aiutare, i seguenti schemi, con tutte le pecche della mia scarsa attitudine alla grafica, vorrebbero aiutare a visualizzare le differenze. Nel primo le frecce dovrebbero sottolineare come il computer sia costretto a tornare a valutare ogni singola condizione:


Qt_IV_if-else_01.png


Nel secondo invece si mostra che soltanto nella peggiore delle ipotesi le condizioni vengono valutate tutte, mentre nella migliore ne viene valutata una sola:


Qt_IV_if-else_02.png


Si può quindi concludere che quando abbiamo una serie di condizioni da valutare, se usiamo la seconda strada e spostiamo all’inizio quelle più probabili, forse il nostro programma sarà di qualche millesimo di secondo più veloce :-)

Scherzi a parte, nel caso il codice da eseguire sia veramente tanto, anche le piccole ottimizzazioni possono portare qualche vantaggio.

L’argomento comunque non riguarda tanto il C++ quanto la programmazione in generale, per cui ora lo lasciamo e torniamo alla nostra class.

Creiamo le condizioni iniziali

Riassumendo: abbiamo bisogno di metodi che modifichino le proprietà ‘x’, ‘y’ e ‘carica’ quando si ‘muove’ il robottino; inoltre abbiamo bisogno di un metodo che riepiloghi questi dati a schermo; poi abbiamo bisogno anche di un metodo che chieda all’utente verso dove vuole spostare il robottino e provveda a fare ciò che l’utente chiede.


Cominciamo:

void vaiSu()
 {
    y += 1;
    carica -= 5;
 }

Questo non era difficile. Con lo stesso criterio possiamo scrivere vaiDx(), vaiSx(), vaiGiu().

Per riepilogare i dati a schermo, si potrebbe pensare a una funzione su per giù come questa:

void datiASchermo()
 {
    cout << "Robottino '" << nome << "':" << endl;
    cout << "   posizione: x = " << x << ", y = " << y << endl;
    cout << "   carica residua: " << carica << "%" << endl;
    cout << endl << endl;
 }

Come vi sembra? Guardiamola assieme.

La prima riga, supponendo che il contenuto della proprietà ‘nome’ sia “Mario” dovrebbe mandate a schermo (cout):

Robottino 'Mario':

Il secondo rigo, supponendo che ‘x’ sia ancora 0 e ‘y’ sia ancora 0, dovrebbe scrivere:

   posizione x = 0, y = 0;

Guardate bene il codice e ditemi se ci trovate qualcosa che non va, perché, ve l’annuncio fin d’ora, ci aspetta una sorpresa.

La terza riga dovrebbe far comparire a schermo il valore di ‘carica’; quindi, quando ‘carica’ è 100:

    carica residua: 100%

Bene. La sorpresa …non dovrebbe essere una sorpresa :-)

‘x’ e ‘y’ non sono altro che variabili, pure locali a un blocco di codice (una class), perciò, come regola del C++ il loro valore non è inizializzato: ossia, contengono dei valori binari a caso, che quasi mai saranno pari a 0. Lo stesso si può dire di ‘carica’, che noi vogliamo pari a 100 quando il robottino ‘nasce’.


L’unica maniera di ottenere questo risultato è scrivere, da qualche parte del codice:

x = 0;
 y = 0;
 carica = 100;

Ma in quale punto del codice? Perché il nostro problema è che noi vogliamo che tutti i robottini nascano così. Quindi deve essere qualcosa che succede in automatico, non appena il robottino viene creato con una scritta del tipo:

Robot rbt;

Esisterà un modo per farlo? Voglio dire, le class sono blocchi di codice con funzioni e variabili, perciò non saremo mica i primi che hanno bisogno che il loro oggetto nasca con le variabili già impostate a determinati valori! Sarà previsto un meccanismo per farlo.

Beh, certo, esiste, e a dire il vero non è un argomento che possiamo evitare di trattare. Però lo possiamo rimandare di qualche altro rigo e analizzare un paio di metodi sostitutivi i cui difetti ci consentono di capire fin da subito quanto è comodo il sistema ‘vero’.


La prima cosa che potremmo pensare di fare è inizializzare le nostre variabili al momento della nascita:

class Robot {
    int x = 0;
    int y = 0;
    int carica = 100;

Se ci fa fatica scrivere tanti righi, possiamo risparmiare un po’ di tempo:

int x = 0, y = 0, carica = 100;

Con quest’ultima grafia, tutte le variabili fino al punto e virgola prendono il tipo che è specificato all’inizio, ossia diventano tutte int. Se avessimo scritto:

string testo1, testo2, testo3;

avremmo creato tre variabili di tipo string, indipendentemente dal nome che avessimo dato alle variabili. L’importante è ricordarsi di mettere una virgola tra l’una e l’altra, perché il punto e virgola serve per chiudere l’istruzione.


Questo sistema per inizializzare le variabili di una class è molto comodo e intuitivo, ma dovremmo sempre cercare di ragionare in stile C++. Se la classe non l’avessimo scritta noi, ma ci trovassimo a usare quella scritta da qualcun altro, con il divieto di modificarla perché serve anche ad altri ancora, cosa penseremmo di questo codice?

Che cosa succederebbe se avessimo necessità di far nascere un robottino in un punto diverso dal centro della stanza, ossia da x = 0, y = 0? Come potremmo fare? Se lo spostassimo subito, grazie ai metodi di spostamento che abbiamo visto prima, influenzeremmo anche la ‘carica’, che risulterebbe più bassa di 100.

Altrimenti dovremmo scrivere una funzione apposita, tipo spostaSenzaToccareLaCarica(int valoreX, int valoreY):

void spostaSenzaToccareLaCarica(int valoreX, int valoreY)
 {
    x = valoreX;
    y = valoreY;
 }

e poi potremmo usarlo in un modo simile a questo:

int main()
 {
    Robot rbt;
    rbt.spostaSenzaToccareLaCarica(3, 5);

…

Ciò potrebbe essere tollerato dagli altri, visto che una funzione in più non dà noia se non la si usa, ma implica aver la possibilità dimettere le mani nel codice scritto da qualcun altro, cosa che non sempre è possibile – tanto per fare un esempio noi useremo gli oggetti che ci verranno messi a disposizione dalle librerie Qt, ma non avremo modo di modificarli!


Comunque sia, questa idea di scrivere una funzione apposita non sembra male. Non sarebbe una cattiva idea se le classi fossero pensate con un metodo ‘universale’, una sorta di statoIniziale(), che consentisse di impostare tutte le variabili al valore con cui si vuole che stiano al momento in cui l’oggetto viene creato. Una sorta di funzione che si auto invoca quando l’oggetto viene creato.


In altre parole, a noi farebbe comodo una funzione del tipo:

void statoIniziale(int valoreX, int valoreY, int valoreCarica)
 {
    x = valoreX;
    y = valoreY;
    carica = valoreCarica;
 }

solo che vorremmo che fosse invocata in automatico quando scriviamo:

Robot rbt;

invece di dover scrivere:

rbt.statoIniziale(0, 0, 100);

Sarà possibile ottenere qualcosa del genere?

Come dicevo, è possibile, ma prima di arrivarci diamo un’occhiata anche a un altro sistema, tanto scomodo quanto quello di inizializzare tutte le proprietà al momento della dichiarazione. Ciò che faremo ora, infatti, sarà impostare il valore delle variabili all’interno di un altro metodo che sappiamo per certo che sarà invocato.


Lo ‘stampo’ che abbiamo concepito per creare robottini, la nostra class, prevede infatti che, per riconoscere un robottino dall’altro, si dia subito loro un nome. Pertanto fin da subito l’utente invocherà il metodo setNome(string battezza), e noi possiamo profittare di questo fatto per inserirci il nostro codice di ‘inizializzazione’ delle proprietà:

class Robot{

    …

 public:
    void setNome(string battezza)
    {
       nome = battezza;
       x = 0;
       y = 0;
       carica = 100;
    }

    …

 };

Anche questo sistema sembra molto semplice e indolore, ma non ci vuole molto a intuire che non è universale: come potremmo mai usarlo con quegli oggetti per i quali non è prevista alcuna operazione iniziale?

Ossia, se non possiamo essere sicuri al mille per mille che chi userà la nostra class invocherà come prima cosa un certo metodo, non sappiamo più dove infilare il codice che inizializza le proprietà.


Per ora lasciamo il codice lì. Quel che sappiamo è che, dopo che avremo scritto:

int main()
 {
    Robot rbt;
    rbt.setNome("Mario");
    …

le nostre proprietà conterranno valori scelti da noi e non spazzatura.

Torniamo al metodo che stavamo scrivendo prima di questa lunga digressione:

void datiASchermo()
 {
    cout << "Robottino '" << nome << "':" << endl;
    cout << "   posizione: x = " << x << ", y = " << y << endl;
    cout << "   carica residua: " << carica << "%" << endl;
    cout << endl << endl;
 }

Adesso magari pensiamo che funzionerà, visto che dopo

rbt.setNome("Mario");

‘x’ sarà diventata 0, ‘y’ invece pure e ‘carica’ sarà 100…

E infatti funzionerà :)


Un’occhiata al codice

Penso che non ne possiate più di codice scritto a bocconcini e vogliate dare una prima occhiata complessiva al nostro programmino, perciò eccolo qui:

#include <iostream>

 using namespace std;

 class Robot{
    string nome;
    int carica;
    int x;
    int y;

 public:
    void setNome(string battezza)
    {
       nome = battezza;
       x = 0;
       y = 0;
       carica = 100;
    }

    string getNome() { return nome; }

    void vaiSu()
    {
       y += 1;
       carica -= 5;
    }

    void vaiGiu()
    {
       y -= 1;
       carica -= 5;
    }

    void vaiSx()
    {
       x -= 1;
       carica -= 5;
    }

    void vaiDx()
    {
       x += 1;
       carica -= 5;
    }

    void datiASchermo()
    {
       cout << "Robottino '" << nome << "':" << endl;
       cout << "   posizione: x = " << x << ", y = " << y << endl;
       cout << "   carica residua: " << carica << "%" << endl;
       cout << endl << endl;
    }
 };

 int muoviRobot(Robot &rbt);

 int main()
 {
    Robot rbt;
    rbt.setNome("Mario");

    while(muoviRobot(rbt))
       rbt.datiASchermo();

    return 0;
 }

 int muoviRobot(Robot &rbt)
 {
    string testo;
    cout << "Direzione? ";
    cin >> testo;

    if("a" == testo) {
       rbt.vaiSx();
       return 1;
    } else if ("w" == testo) {
       rbt.vaiSu();
       return 1;
    } else if ("s" == testo) {
       rbt.vaiDx();
       return 1;
    } else if ("z" == testo) {
       rbt.vaiGiu();
       return 1;
    } else if ("0" == testo) {
       return 0;
    }
    return 2; // 2 == tasto non riconosciuto
 }

Nella prima parte non ci dovrebbe essere nulla di strano: la class Robot è composta dal codice che si è discusso fino a qui. Invece le funzioni main() e muoviRobot() potrebbero non essere del tutto chiare alla prima occhiata, per cui ce ne occuperemo subito.


La funzione muoviRobot() si occupa di chiedere all’utente di digitare del testo, nello specifico una singola lettera (‘a’, oppure ‘w’ o ‘s’ o ‘z’), seguita ovviamente dal tasto INVIO, dopodiché invoca il metodo del Robot che ‘sposta’ virtualmente l’immaginario robottino nella direzione desiderata.


Dopo aver creato una variabile di tipo string denominata ‘testo’, la funzione scrive a schermo la parola “Direzione?” (cout << "Direzione? ";).

La risposta dell’utente viene inserita nella variabile ‘testo’ (cin >> testo;).

Il contenuto di questa variabile viene poi controllato da una serie di if-else, ma la discussione su questo punto l’abbiamo già svolta prima.


Guardiamo insieme come è stata pensata la logica dei valori di ritorno.

Se la funzione è in grado di capire cosa vuole l’utente – che significa: se il contenuto della variabile ‘testo’ è a, w, s o z – allora la funzione restituisce 1.

Se l’utente sceglie di uscire digitando “0”, allora la funzione restituisce 0.

In tutti gli altri casi, restituisce 2.


Ricordiamoci che quando il flusso di esecuzione dei comandi incontra un’istruzione return, la funzione viene chiusa e il flusso ritorna al codice chiamante.


Diamo un’occhiata anche a main().

Le prime due istruzioni le abbiamo viste all’inizio di questa sezione del tutorial e non credo si debba tornarci sopra. Invece il blocco

while(muoviRobot(rbt))
    rbt.datiASchermo();

può lasciare un po’ interdetti.

L’istruzione

while(muoviRobot(rbt))

significa: “finché muoviRobot ( rbt ) è vero…”

Si è già detto che si usa considerare zero come falso, e tutto ciò che non è zero come vero. Perciò la stessa istruzione avrebbe potuto essere scritta così:

while( 0 != muoviRobot(rbt) )

e avrebbe significato esattamente la stessa cosa (ne avevamo già parlato, ricordate?).

A sua volta la funzione muoviRobot() restituisce 0 solo quando l’utente digita “0”, altrimenti restituisce o 1 o 2 – e quindi valori diversi da zero.

La conclusione è che

finché l’utente non digita zero…

si esegue il blocco di codice while.


Il blocco di codice che segue è composto da una sola riga e in questi casi il C++ ci autorizza a non chiuderlo tra parentesi graffe.

Quindi questo codice:

while(muoviRobot(rbt))
    rbt.datiASchermo();

è assolutamente identico a questo:

while(muoviRobot(rbt))
 {
    rbt.datiASchermo();
 }

Il motivo per cui non ho usato le parentesi graffe è solo dovuto al fatto che è lecito, nel C++, non usarle se tutto il codice che segue un for o un if o uno while o simili è composto da una sola riga. È lecito non significa che è consigliato. Anzi! Bisogna stare bene attenti che il nostro codice sia sempre leggibile con chiarezza.


Cosa dice questo blocco di codice?

finché muoviRobot() non restituisce 0, continua a chiamare il metodo datiASchermo() del robottino ‘rbt’.

In questa maniera si fa ‘muovere’ il robot e si comunica la posizione e la carica residua all’utente.


Vorrei evidenziare, a chi non l’avesse notato, l’uso del reference per l’argomento della funzione muoviRobot(). Come vedete non c’è nessuna differenza fra un oggetto creato da noi e uno di quelli che il C++ ci fornisce già pronti, come string (a parte, ovviamente, la qualità del prodotto); come non c’è differenza nella sintassi con cui si usano gli oggetti o le variabili ‘normali’.

Di un oggetto si può creare un alias tramite un reference e con questa tecnica può essere modificato dalla funzione a cui viene passato. Se riguardate il tutorial precedente, vedrete che la sintassi usata lì è identica a questa sia per l’argomento che per il parametro. Possiamo quindi stare tranquilli che i metodi invocati da muoviRobot() sono proprio quelli del robottino ‘rbt’ creato in main() e la progressiva diminuzione del valore di ‘carica’ ce lo conferma.


Non male: abbiamo creato il nostro primo ‘stampo per oggetti’ nel C++ (una class) e un primo oggetto… e tutto pare funzionare!

Adesso non ci resta che migliorarlo.


Prima però, l’ultima cosa noiosa: un parolone.

Il parolone degli oggetti

Lo abbiamo già detto all’inizio della sezione, ma non fa male ripeterlo. Una class è uno stampo per creare oggetti di un certo tipo (nel nostro esempio, robottini immaginari). Ciò implica che di suo non è un oggetto.

Invece ‘rbt’ era l’oggetto creato con lo stampo class Robot.


Allora mettiamoci d’accordo su questa parola: creare un oggetto tramite il suo stampo si dice istanziare un oggetto.

Una variabile come ‘rbt’ si definisce una istanza della class Robot.


Brutto ‘istanziare’, vero? Beh, anche a essere teneri, non c’è modo di considerarlo meno che orrendo. Sarà una mia fissa, ma anche in questo caso l’assonanza con l’inglese instance (che vuol dire ‘esempio’, ‘caso’) mi fa sospettare che esista un qualche segreto dell’essere informatici che non ho ancora capito, tipo dover giurare che mai e poi mai si farà uso di un vocabolario nella propria vita.

Il problema di questo neologismo è che in italiano già esisteva la parola istanza e con un significato mille miglia lontano da ciò di cui stiamo parlando (chi non ha mai sentito parlare delle istanze sociali o di una istanza a una Pubblica Amministrazione?). Di conseguenza può non essere sempre semplice, quando si sente parlare di una istanza di classe, capire se si discuta delle pressanti esigenze di un ceto sociale o di un blocco di codice in C++, ma dovremo farcene una ragione.


Infatti l’uso è invalso, perciò ci dobbiamo adattare al fatto che un esempio di un tipo debba divenire un’istanza di una classe… E non pensiamoci più.


Il costruttore

Abbiamo lasciato una domanda in sospeso.

Se una class è un blocco di codice con delle variabili e questo blocco funziona come ‘stampo’ per creare oggetti, è possibile fare in modo che le variabili contenute in quel blocco nascano con un determinato valore iniziale non appena si crea l’oggetto?


Ci serve un metodo

Sì, è possibile, il sistema è semplice e non c’è più motivo per girarci intorno.

In buona sostanza le cose stanno così: esiste una funzione, o meglio un metodo, visto che stiamo parlando di class, che si auto invoca quando l’oggetto viene creato (istanziato).


Questo metodo che si auto invoca può quindi essere usato per tutte le operazioni che vogliamo che il nostro oggetto compia alla nascita.

Dirò di più: questo metodo, come tutte le funzioni, può prevedere degli argomenti, perciò è possibile personalizzare la nascita di ogni singolo oggetto anche quando stiamo usando sempre la stessa class.


A questo punto vi domanderete: ma se le cose erano così semplici, perché non ce l’ha detto subito, questo fessacchiotto?

Perché l’argomento è semplice, ma ha una particolarità che merita due righe di spiegazione: il nome di questo metodo è un po’ strano.


Qualcuno di voi, facendo caso al titolo di questo capitolo, potrebbe pensare che il nome in questione sia costruttore(), magari all’inglese: constructor(). E ci avrebbe azzeccato a metà! Infatti ci si rivolge a questo metodo come al ‘costruttore’ della class, ma non è il suo nome vero, bensì un termine generico, anche se tecnico, che deriva dal suo ruolo.

In realtà questo metodo costruttore non ha un vero e proprio nome, o per meglio dire: il suo nome cambia di class in class. Il costruttore di string non ha lo stesso nome del costruttore di vector e anche noi dovremo scegliere con molta cura il nome del costruttore della nostra class Robot, perché il C++ è molto, molto pignolo in questa faccenda!


Riuscite a indovinare quale nome potremo dare a questo metodo che si auto invoca quando si istanzia un oggetto? Beh, ovviamente lo stesso nome della class!

class Robot{
    string nome;
    int carica;
    int x;
    int y;

 public:
    Robot();

    void setNome(string battezza)
    {
       …

Eccolo lì! Quel

Robot();

è il nostro costruttore. Essendo il costruttore di Robot, il suo nome è Robot(). Se adesso decidessimo di costruire uno ‘stampo’ per un oggetto ‘OggettoCheNonSoBeneCosaSia’, dentro il suo blocco di codice ci sarà posto per una funzione OggettoCheNonSoBeneCosaSia(); questa funzione sarà il costruttore della classe.

class OggettoCheNonSoBeneCosaSia {
     OggettoCheNonSoBeneCosaSia();
 }

Proviamo a riscrivere la nostra classe spostando il codice di inizializzazione delle proprietà dalla funzione setNome() al metodo costruttore Robot():

Robot()
 {
    x = 0;
    y = 0;
    carica = 100;
 }

A questo punto la funzione setNome() rimarrà:

void setNome(string battezza) { nome = battezza; }

Possiamo provare a far girare il programma per verificare che tutto funzioni come prima.


Cambiamo argomenti

Guardiamo un po’ più a fondo il nostro costruttore. Nel JS abbiamo visto che per creare un oggetto potevamo usare una funzione prototipo, detta anche constructor function, che accettava parametri:

function Robottino (pa_nome) {
     var nome = pa_nome;

In quel momento non ci era sembrato nulla di strano: le funzioni possono avere argomenti, si sa. Passando al C++, quello che può apparire un po’ forzato è invece che tramite il metodo costruttore riusciamo a passare parametri alla class.

Guardiamolo da un altro punto di vista. Le nostre variabili ‘x’, ‘y’ e ‘carica’ vengono impostate agli stessi valori per tutti i robottini. La proprietà ‘nome’ invece no: sarà diversa per ogni robottino.

Sarebbe possibile impostarla al valore desiderato fin dalla nascita e senza stare a invocare un metodo scritto a tale scopo? Sì, certo: è possibile tramite il costruttore.

Si deve aggiungere un argomento di tipo string al costruttore in modo che questo possa ricevere il nome che si desidera dare al robottino e lo possa inserire nella variabile ‘nome’.

Ma se il costruttore si auto invoca, qual è la sintassi per passargli i parametri?


In effetti è stata creata una sintassi specifica per questo problema: in pratica si passano i parametri direttamente alla classe e questi verranno recapitati agli argomenti del costruttore.

Nel nostro caso, se modifichiamo il costruttore Robot() in questo modo:

Robot(string pa_nome)
 {
    nome = pa_nome;
    x = 0;
    y = 0;
    carica = 100;
 }

possiamo anche sostituire le prime righe di main() da così:

int main()
 {
    Robot rbt;
    rbt.setNome("Mario");

a così:

int main()
 {
    Robot rbt("Mario");

ottenendo esattamente lo stesso risultato.


In pratica il parametro che passiamo al momento della creazione dell’oggetto (non ce la faccio proprio a dire istanziazione, mi fa male al fegato) viene spedito pari pari al costruttore.

Quindi sarà accettato solo se corrisponde a quello previsto dal costruttore – il ché vorrebbe dire, nel nostro caso, che un’istruzione del tipo:

Robot rbt(5);

darebbe un errore, perché nel costruttore è specificato che l’argomento è una string, pertanto il compilatore non ci consentirà di inserire un int.


Se vogliamo, possiamo guardare le cose anche alla rovescia e dire che una class, grazie al suo costruttore, è in grado di accettare parametri. Da un punto di vista concettuale non è errato e la sintassi pensata allo scopo conduce proprio a questa conclusione: infatti i parametri vengono passati direttamente alla class.

Si può anche arrivare a dire che creare un oggetto è all'incirca come invocare il metodo costruttore della class di quell’oggetto… Tutte affermazioni all’atto pratico non sbagliate che servono solo per ricordarsi meglio cosa succede quando si usa una certa sintassi.


Quindi: una class non è una funzione; quando si scrive il relativo codice, non abbiamo una parentesi tonda prima della graffa dove inserire gli argomenti che vorremmo usare – Sull’altro lato della medaglia, però, scopriamo che: una class prevede un metodo costruttore che ha il suo stesso identico nome (comprese maiuscole e minuscole!); il metodo accetta argomenti; i parametri per tali argomenti possono essere passati al momento della creazione dell’oggetto con una sintassi che ricorda quella delle funzioni.

In base a tale sintassi potremmo discutere all’infinito se stiamo, da un punto di vista concettuale, stiamo passando i parametri alla class (che non li prevede) o al costruttore della class (che non è una dichiarazione di tipo): ciò che conta è che ci ricordiamo come si fa.


E se non voglio il costruttore?

Qualcuno potrebbe domandarsi in quali casi convenga aggiungere un costruttore alla classe.

In altre parole: conviene iniziare a scrivere una class senza il costruttore e poi magari aggiungercelo all’esigenza, o conviene partire dal presupposto che una class dovrebbe avere il suo bel costruttore?


A titolo personale ritengo che sia meglio specificare fin da subito un costruttore. In fondo, se sul momento non abbiamo un compito specifico da fargli svolgere, basta scrivere un rigo che si limita a ripetere il nome della classe, ci aggiunge un paio di parentesi tonde, un punto e virgola e fine.

class Qualsiasi{
    …
 public:
    Qualsiasi();
    …

}

Questo tipo di costruttore si chiama costruttore di default. Non è una grossa incombenza. Quando poi capita di dover modificare la class e fargli fare qualcosa, è lì pronto che ci aspetta.


Se però faceste la stessa domanda al compilatore, beh, è probabile che otterreste una risposta del tutto diversa: infatti dal suo punto di vista nel C++ una class deve avere il costruttore. Non può esistere una class senza il costruttore.

Infatti, se non ce lo mettiamo noi… deve aggiungercelo lui!

Una delle comodità dei compilatori è che, se incappano in una class senza il costruttore, al momento della trasformazione in linguaggio macchina ce lo aggiungono loro. Quindi, nel codice scritto da noi non c’era, ma nel programma eseguito dal pc sì: ce l’aveva aggiunto il compilatore.

È ovvio che i compilatori aggiungono il codice di un costruttore di default, ossia l’equivalente di quello che avremmo ottenuto scrivendo:

Robot();

Questo costruttore si chiama implicit default constructor. Implicit può essere tradotto implicito, sottinteso, ma anche tacito o addirittura assoluto; in questo caso mi sembra che sottinteso sia il significato che più si avvicina all’originale.


Se avessimo costruito la class usando la procedura assistita fornita da Qt Creator, ci saremmo trovati con il nostro bel costruttore, magari vuoto, inserito nel codice della classe. Poiché il costruttore è utile, infatti, gli IDE ci aiutano a non dimenticarcene.

Se adesso state pensando: “Uh, che bello! Allora non starò mai a scrivere un costruttore, tanto ce lo inserisce il compilatore in automatico”, mi sento di obiettare subito: idea discutibile.

Quando userete la classe di qualcun altro, vi farà piacere che ci sia un costruttore; impariamo a inserircelo anche noi.

Queste piccole fatiche non ci dovrebbero scoraggiare, perché nel C++ sono previste bestioline ancora più ostiche come i costruttori in copia e i costruttori di spostamento, che non siamo obbligati a trattare ora perché nelle librerie Qt li troveremo già pronti e impacchettati.

Anche il costruttore in copia, se non lo scriviamo noi, viene aggiunto nel codice eseguibile dal compilatore, quindi per ora lo ignoreremo.


Ma guarda che tipo!

Scommetto che pensate che me ne sia dimenticato, vero?

Ebbene, no: il costruttore è fatto proprio così: non ha un valore di ritorno.

Si tratta dell’unica eccezione alla regola che per tutte le funzioni si debba indicare il valore di ritorno, e se non restituiscono nulla vada scritto void.


Quindi, se per main() è sempre int, cascasse il mondo, per un costruttore non è mai indicato alcun tipo di ritorno. Tutte le altre funzioni restituiscono il valore che piace di chi le scrive.


Se vi chiedete come mai sia stata scelta questa particolare sintassi, quando sarebbe stato possibile stabilire di dichiarare tutti i costruttori void e non creare altre eccezioni nella regola generale delle funzioni, vi confesso che non lo so. Cercando su Internet forse una storia di come si sia giunti a questa decisione si potrebbe trovare, ma ancora non mi ci sono dedicato.

Qui su due piedi vorrei però spezzare una lancia a suo favore, perché non è illogica. Un costruttore da chi viene invocato? In realtà da nessuno, in quanto che è chiamato direttamente dal compilatore quando deve costruire un’istanza di una class. Come dicevamo all’inizio per semplificare, si auto invoca.

Ma allora, se non è invocato da nessuno, a chi dovrebbe restituire il suo valore di ritorno? Sempre a nessuno. Quindi è logico che non lo abbia. Almeno mi sembra.


Il costruttore è un metodo particolare, che ha un nome speciale, viene eseguito sempre (al momento della creazione dell’oggetto), ma una volta sola, e poi, si può dire, esaurisce il suo compito: non è possibile invocarlo direttamente dal programma.


E lo stesso si può dire, in modo speculare, per il suo fratello gemello: il distruttore.


Il distruttore

Beh, a sentirlo nominare c’è da aver paura. Il “distruttore”. Sembra un cattivone della Marvel.


Innanzitutto è bene sfatare il mito: distruttore in realtà sarebbe una congrua traduzione per la parola inglese destroyer (che, per curiosità, indica anche un tipo di nave, il cacciatorpediniere). In questo caso, invece, stiamo trattando un tema che in quella lingua si definisce destructor, e nella nostra potremmo più propriamente rendere con inceneritore. Altre oscure ragioni che possano trasformare nella mente degli informatici un inceneritore in un distruttore, eccetto l’assonanza con l’originale inglese, non ne trovo. Pertanto m’impegno fin da ora a cercare il testo del giuramento formale di non far mai uso di un vocabolario, pena la scomunica, che viene con ogni evidenza firmato col sangue da tutti gli informatici.


In realtà si tratta di una funzione utilissima in certe situazioni, ma non sempre usata. Il suo compito è quello di garantire che la cancellazione dell’oggetto dalla memoria, quando non serve più, avvenga con tutti i crismi. V’immaginate se, in un programma molto grande, non si riuscisse via via a liberare la memoria dagli oggetti che non servono più? In poco tempo il computer sarebbe costretto a innalzare bandiera bianca. E di programmi molto, molto grandi con il C++ ne sono stati scritti parecchi. Per fortuna, come abbiamo già visto, via via che le funzioni finiscono, le variabili, e quindi anche gli oggetti, creati localmente vengono rimossi.

Il problema è che un oggetto può essere grosso e complicato. Non guardate il nostro, che ha poche variabili e pochissime funzioni: pensate a un vector, che può contenere una mole impressionante di dati, e forse non è nemmeno fra gli oggetti più complessi.


Quando il computer decide di cancellare un oggetto, può trovarsi davanti a un bel compito. Di solito ce la fa benissimo da solo, ma in alcuni casi conviene cercare di aiutarlo. Per farlo, possiamo inserire del codice apposito nel metodo destructor; infatti, quando deve eliminare un oggetto, il computer va a leggere cosa c’è scritto nel destructor e lo esegue.

Il destructor è quindi l’inceneritore che serve per smaltire in modo corretto gli oggetti che non servono più.


Il nostro oggetto è molto semplice e il computer non ha bisogno di aiuto per incenerirlo, ma per capire come funziona un distruttore possiamo provare a scriverci del codice dentro e a vedere cosa succede.


Cominciamo col dargli un nome; non possiamo mica chiamarlo ‘distruttore’, vi pare?

Beh, qui il C++ ci viene in aiuto: infatti il destructor accetta di essere chiamato solo come il costruttore, ossia con lo stesso nome della classe. Per farsi riconoscere rispetto al costruttore, però, chiede di essere contrassegnato da un simbolo particolare: la tilde ‘~’. La tilde è un segno che non compare nella lingua italiana, ma in molte altre sì: tanto per fare degli esempi, nella spagnola e nella portoghese. Anche in matematica ha dei significati.

Nel C++ serve solo a questo: a distinguere il costruttore dal destructor. Quindi, nel nostro caso il destructor sarà:

~Robot();

Per fare la tilde, non essendo presente nella tastiera italiana, potete usare uno dei seguenti metodi:

  • in Linux c’è una combinazione di tasti apposita: Alt Gr + ì (Alt Gr è il tasto alla destra della barra spaziatrice; la ì accentata si trova invece in alto, alla destra del punto interrogativo);
  • in Mac Os X la combinazione dovrebbe essere Alt + 5 (non ho il Mac, quindi sto riportando quanto si trova su Internet);
  • in Windows le cose sono, ma chi l’avrebbe mai detto?, più complicate: se avete il tastierino numerico, allora, tenendo premuto il tasto ALT, digitate 126; se non avete il tastierino numerico, provate a guardare se sulla vostra tastiera compaiono dei caratteri piccoli di colore azzurro; se ci sono, tenendo premuto ALT + fn, digitate 126 tramite quei caratteri azzurri. Se non funziona… mandate una mail sig. Gates.

Che codice potremmo mai inserire nel nostro destructor? Beh, visto che l’incenerimento del nostro oggetto ‘rbt’ è dovuto all’uscita da main(), e quindi alla fine del programma, potremmo lasciare dei saluti:

~Robot()
 {
    cout << endl << endl;
    cout << "Torna presto a trovarci!" << endl << endl;
 }

Aggiungete il metodo precedente in un punto qualsiasi della class Robot –ma non all’interno di una altro metodo!– e state a guardare cosa succede quando il programma termina.


Qt_IV_09.png


Il nostro educato robottino saluta prima di sparire.


Bene, abbiamo fatto la conoscenza anche con il destructor, che presenta le stesse caratteristiche del costruttore:

  • non si può invocare direttamente;
  • viene sempre chiamato direttamente dal computer quando un oggetto viene eliminato;
  • viene chiamato una volta sola;
  • ha lo stesso nome della class, ma preceduto da tilde;
  • non ammette valori di ritorno.

Questo è il riepilogo delle modifiche:

#include <iostream>

 using namespace std;

 class Robot{
    string nome;
    int carica;
    int x;
    int y;

 public:
    Robot(string pa_nome)
    {
       nome = pa_nome;
       x = 0;
       y = 0;
       carica = 100;
    }

    void setNome(string battezza) {nome = battezza;}

    string getNome() { return nome; }

    void vaiSu()
    {
       y += 1;
       carica -= 5;
    }

    void vaiGiu()
    {
       y -= 1;
       carica -= 5;
    }

    void vaiSx()
    {
       x -= 1;
       carica -= 5;
    }

    void vaiDx()
    {
       x += 1;
       carica -= 5;
    }

    void datiASchermo()
    {
       cout << "Robottino '" << nome << "':" << endl;
       cout << "   posizione: x = " << x << ", y = " << y << endl;
       cout << "   carica residua: " << carica << "%" << endl;
       cout << endl << endl;
    }

    ~Robot()
    {
       cout << endl << endl;
       cout << "Torna presto a trovarci!" << endl << endl;
    }
 };

 int muoviRobot(Robot &rbt);

 int main()
 {
    Robot rbt("Mario");
    // rbt.setNome("Mario");

    while(muoviRobot(rbt))
       rbt.datiASchermo();

    return 0;
 }

 int muoviRobot(Robot &rbt)
 {
    string testo;
    cout << "Direzione? ";
    cin >> testo;

    if("a" == testo) {
       rbt.vaiSx();
       return 1;
    } else if ("w" == testo) {
       rbt.vaiSu();
       return 1;
    } else if ("s" == testo) {
       rbt.vaiDx();
       return 1;
    } else if ("z" == testo) {
       rbt.vaiGiu();
       return 1;
    } else if ("0" == testo) {
       return 0;
    }
    return 2; // 2 == tasto non riconosciuto
 }


Puntatori a oggetto

Nel nostro programmino abbiamo usato un reference ad un oggetto (&rbt). Secondo voi è possibile anche avere puntatori agli oggetti?

Domanda scontata? :)


In effetti abbiamo già incontrato puntatori a string nell’ultimo tutorial e abbiamo già anticipato che la sintassi cambia leggermente: se vogliamo usare un puntatore, non possiamo raggiungere i membri dell’oggetto tramite un punto, ma dobbiamo servirci dello strano operatore ‘->’.

In effetti una classe è un blocco di codice con un’etichetta, perciò non può non avere un indirizzo di memoria; quell’indirizzo di memoria è solo un numero, che può benissimo essere passato a un puntatore.


La sintassi per dichiarare un puntatore a oggetto è identica a quella che abbiamo incontrato fin qui:

Robot* rbt;

Dopodiché, come dicevo, per invocare un metodo come vaiSx(), dovremmo scrivere:

rbt->vaiSx();

e otterremo lo stesso risultato.


La domanda che a qualcuno potrebbe venire è se si possono avere dei puntatori all’interno dell’oggetto. In altre parole, una delle nostre variabili, a esempio ‘carica’, poteva essere:

int* carica;

?

Sì, certo, poteva. Una classe può contenere un po’ di tutto. Abbiamo dichiarato dei puntatori all’interno delle funzioni, poi delle funzioni all’interno di una classe… Viene abbastanza naturale supporre che un puntatore può essere dichiarato all’interno di una classe :-)

Ricordiamoci però che, finché non lo inizializziamo, i byte che compongono il nostro puntatore contengono una sequenza ignota di cifre, perciò sta puntando ad uno spazio a caso della memoria dove può essere contenuta qualsiasi cosa. Usare un puntatore soltanto per sostituire un int di solito non è molto vantaggioso, a meno che non vogliamo accedere a quello int da diversi scope, spazi di visibilità.

Però… ragioniamoci un po’ su. Una variabile dichiarata all’interno di una class, anche se privata, è visibile da tutte le funzioni membro di quella class… Quindi, ognuna di esse può usarla come se fosse propria.


Allora possiamo intanto giungere a questa conclusione: le funzioni dichiarate all’interno di una class hanno una specie di vantaggio rispetto alle funzioni ‘normali’ perché, oltre alle variabili globali e a quelle locali (compresi gli argomenti), possono vedere anche le variabili che non sono locali a loro, ma lo sono rispetto alla class. Sto parlando di quelle variabili, come ‘carica’, che vengono solitamente chiamate proprietà della class.


Godendo di questa situazione vantaggiosa, dichiarare puntatore una proprietà di una class ci può dare dei vantaggi concreti?

Non so se mi sono espresso correttamente. Il vantaggio dei puntatori è che riusciamo, come dire, a ‘muoverli’ da una funzione all’altra – giacché quello che viene copiato nell’argomento è un indirizzo, usando i puntatori riusciamo ad accedere sempre alla stessa cella di memoria, che vuol dire “allo stesso valore”, da tutte le funzioni.

Ma all’interno di una class tutti metodi possono accedere alle proprietà, quindi che vantaggio ci sarebbe a farne dei puntatori?


Le esigenze che nascono quando si comincia a programmare sono così tante che può capitare di scrivere e leggere di tutto; non ci sorprendiamo perciò a scoprire che i puntatori all’interno delle class sono molto usati. I loro impieghi sono molteplici, ma per ora ci basti pensare all’eventualità di voler tenere di mira qualcosa che è esterno alla classe – funzione che un puntatore può assolvere benissimo.


Quale potrebbe essere la loro sintassi? Mettiamo il caso che crei una class con un puntatore – per comodità lo dichiaro public, così posso evitare di scrivere metodi per usarlo (ma non è questa la logica degli oggetti!):

class ACaso {
 public:
    int* puntatore;
 }; // ricordarsi questo punto e virgola!

Mettiamo che trovi un qualcosa da fargli puntare:

int main()
 {
    // Creo una variabile intera con un nome e un valore a caso
    int intQualsiasi = 17;

    // Creo un oggetto tramite il mio ‘stampo’ ''class'' ACaso.
    ACaso acs;

    // Passo l’indirizzo della variabile intera definita prima 
    // al puntatore contenuto nella mia ''class''.

    acs.puntatore = &intQualsiasi;    // Da questo momento posso usare
                                      // acs.puntatore per accedere 
                                      // al valore di intQualsiasi.
 }

Spero che il codice soprastante non crei difficoltà a nessuno. Sono tutte cose di cui abbiamo già parlato.

Adesso possiamo provare a usare il nostro puntatore per accedere ai dati della variabile puntata come abbiamo sempre fatto, ossia usando l’asterisco (‘*’), che in questo caso prende il nome di simbolo di deferenziazione.

   cout << "intQualsiasi == " << *acs.puntatore << endl << endl;

Il risultato è esattamente quello atteso.


Qt_IV_10.png


Sembra tutto a posto e in effetti lo è. Avete notato, però, dove è andato a finire il nostro simbolo di dereferenziazione?

*acs.puntatore

Si è spostato prima del nome della variabile che rappresenta l’istanza della classe. Questa è una nuova sintassi da imparare: non è nomevariabile-punto-asterisco-nomepuntatore, ma asterisco-nomevariabile-punto-nomepuntatore.

Se provate a scrivere

acs.*puntatore

vi dà errore.

Adesso però proviamo a trasformare il codice di main da così

int main()
 {
    // Creo una variabile intera con un nome e un valore a caso
    int intQualsiasi = 17;

    // Creo un oggetto tramite il mio ‘stampo’ class ACaso.
    ACaso acs;

    // Passo l’indirizzo della variabile intera definita prima
    // al puntatore contenuto nella mia class.

    acs.puntatore = &intQualsiasi;    // Da questo momento posso usare
                                      // acs.puntatore per accedere
                                      // al valore di intQualsiasi.
    cout << "intQualsiasi == " << *acs.puntatore << endl << endl;
 }

a così

int main()
 {
    // Creo una variabile intera con un nome e un valore a caso
    int intQualsiasi = 17;

    // Creo un oggetto tramite il mio ‘stampo’ class ACaso.
    ACaso* acs;
 …

Quindi trasformando ‘acs’ in un puntatore. Prima di andare avanti, soffermiamoci su cosa stiamo facendo.

Se vi ricordate la lunga spiegazione del tutorial precedente, vi verrà il dubbio di scoprire se il nostro puntatore sta puntando a un’area di memoria che contiene qualche dato utile oppure no. La risposta è: ovviamente no. Il puntatore appena creato contiene come valore un numero intero casuale, che soltanto una volta ogni innumerevoli tentativi corrisponderà all’indirizzo di una cella di memoria che contiene dei dati salvati da noi.

Infatti noi abbiamo detto al compilatore solo questo: “crea un puntatore che sia idoneo a puntare a un’area di memoria che contenga un’istanza della class ACaso”. Non abbiamo creato nessuna istanza della class ACaso.

Per fare in modo che il nostro puntatore sia connesso a una vera istanza della classe ACaso, dobbiamo crearla e poi passare il suo indirizzo al puntatore:

ACaso acs;
 ACaso* p_acs = &acs;

Ci sarebbe un metodo più breve, che fa uso della parola magica “new”, ma ancora non ne abbiamo parlato perciò non possiamo adottarlo.

Tornando al nostro codice, ora abbiamo un puntatore a una classe (ACaso) che ha tra i suoi membri una proprietà che è un puntatore:

int* puntatore;

Adesso diciamo a ‘puntatore’ di tenere di mira la variabile locale intQualsiasi:

p_acs->puntatore = &intQualsiasi;

La sintassi è cambiata. Avevamo già affrontato questo argomento, ma questo capitolo vorrebbe provare a dare una spiegazione di questo dettaglio.

Dunque, detta in breve: quando la variabile che rappresenta l’istanza della classe è un puntatore, allora per accedere ai suoi membri non si usa un punto (‘.’), bensì la buffa combinazione trattino-maggiore di (‘->’).

Volendo fare un esempio con la classe precedente, Robot, se ne creassimo un’istanza tramite un puntatore

Robot* p_rbt;

dovremmo invocare i suoi membri così:

p_rbt->setNome("Mario");
 p_rbt->datiASchermo();
 p_rbt->vaiSx();

ecc. ecc.

In apparenza può sembrare una sottigliezza di cui non si sentiva il bisogno, ma se teniamo presente che ora l’asterisco che serve per accedere ai dati puntati si è spostato prima del nome della variabile che rappresenta l’istanza della classe, tutto acquista un senso.

Se non ci arrivate subito, guardate un po’ cosa succede al rigo successivo:

cout << "intQualsiasi == " << *p_acs->puntatore << endl << endl;

Cominciate a sospettare dov’è l’inghippo?

Ipotizziamo che non esista la regola di usare il simbolo -> nel caso si crei un’istanza di una classe tramite un puntatore, ma si possa usare il semplice punto. Se fosse così, come andrebbe scritta l’istruzione precedente? Forse così:

cout << "intQualsiasi == " << *p_acs.puntatore << endl << endl;

In tal caso però, provate a chiedervi: “come fa il compilatore a sapere se l’asterisco si deve riferire al puntatore ‘p_acs’ o al puntatore ‘puntatore’?” Quale dei due puntatori dovrebbe dereferenziare?

La sintassi è ambigua, infatti il compilatore si blocca dandoci un errore, tra l’altro molto chiaro:


Qt_IV_11.png


Maybe you meant to use ‘->’ ? è uno dei pochi messaggi d’errore che fornisce un aiuto immediato. Di solito, come avete visto, sono più farraginosi.

Il C++ però non è cattivo come sembra: se avessimo una qualche avversione verso il simbolo ‘->’, il linguaggio ci autorizza a non usarlo. Possiamo continuare a basarci sul semplice punto, però stando attenti a dereferenziare correttamente il puntatore che rappresenta la classe:

(*p_acs).puntatore

è una sintassi del tutto corretta e equivalente sotto ogni aspetto a

p_acs->puntatore

Le parentesi che includono l’asterisco e il nome della variabile consento infatti al compilatore di interpretare ciò che volete, ossia dereferenziare il puntatore che tiene di mira l’istanza di classe in modo da raggiungere uno dei suoi membri, in questo caso la proprietà pubblica ‘puntatore’.

Questo però non vi consente ancora di arrivare al contenuto della variabile intQualsiasi, per raggiungere il quale dovete dereferenziare anche ‘puntatore’:

*(*p_acs).puntatore

L’ultima istruzione funziona, provare per credere:

#include <iostream>

 using namespace std;

 class ACaso {
 public:
    int* puntatore;
 };

 int main()
 {
    int intQualsiasi = 17;

    ACaso acs;
    ACaso* p_acs = &acs;

    p_acs->puntatore = &intQualsiasi;
    cout << "intQualsiasi == " << *(*p_acs).puntatore << endl << endl;
 }

Se la sintassi

*(*p_acs).

vi sembra più chiara di

*p_acs->

siete liberi di usarla: io mi trovo molto meglio con ‘->’, pensato proprio per semplificare la vita!


Adesso però semplifichiamoci la vita fra noi: la lunga locuzione che ho usato fin qui, variabile che rappresenta un’istanza di class, è veramente faticosa da scrivere e da leggere. Un’istanza di una class è un oggetto, cosicché per semplicità si estende questo termine anche alla variabile che lo rappresenta. Perciò, nel nostro esempio, ‘acs’ poteva essere chiamato semplicemente oggetto e ‘p_acs’ puntatore a oggetto.

D’ora innanzi così faremo, rendendo tutto molto più scorrevole.

Conclusioni

Se qualcuno che conosce già il C++ sta scorrendo questo tutorial, nel leggere il titolo di questa sezione avrà subito un’extrasistole. Se infatti quel poco che abbiamo detto sul JS consente già di farsi un quadro di cosa sia un oggetto, nel C++ abbiamo a malapena scalfito la superficie.

Se guardo la lista degli argomenti di cui dovremmo ancora parlare, mi prende male: costruttore in copia e di spostamento, overload delle funzioni e del costruttore, overload degli operatori, ereditarietà, ereditarietà multipla, funzioni virtuali, funzioni virtuali pure, virtual constructor, virtual destructor, classi base astratte, early binding e late binding, polimorfismo, membri static, sintassi della dichiarazioni degli oggetti, member initializer list, classi annidate… Per non parlar poi del successivo livello di astrazione: i template!

Il supporto alla programmazione agli oggetti nel C++ è davvero vasto e rappresenta uno dei suoi punti di forza.


Però di tutorial sul C++ ne esistono moltissimi a giro, tutti scritti da persone più competenti di me, e aggiungerne un altro non sarebbe utile a nessuno. Siamo quindi costretti a operare delle scelte, e la nostra scelta è di saltare a piè pari tutto ciò che non è strettamente indispensabile per maneggiare le librerie Qt.

Chi, forse un po’ anche per merito di questo piccolo tutorial, si fosse appassionato di questo linguaggio, sappia che online ci sono risorse molto più tecniche e complete di questa, che anzi è forse un po’ troppo discorsiva.


A noi ci basta sapere che abbiamo ancora quattro argomenti enormi da trattare, che ci porteranno via ancora un sacco di tempo, per cui di tutto il resto non abbiamo proprio modo di occuparcene:

  1. che cosa significa “using namespace std”;
  2. che cos’è this (nel C++);
  3. come si fa a scrivere una classe su più file;
  4. cosa fa la parola magica new.

Tutti questi argomenti sono connessi in qualche modo agli oggetti, alcuni in modo intimo (this), altri più alla lontana (using namespace std), pertanto li abbiamo rimandati fino a qui, ma ora non possiamo più evitarli.


using namespace std: liberiamocene!

Dovete sapere che il professor Stroustrup è anche il curatore di un sito sul C++ che rappresenta uno dei punti di riferimento di chi vuole muovere i primi passi in questo linguaggio.

L’indirizzo del sito è il seguente:

http://www.stroustrup.com/bs_faq2.html

In seguito il contenuto di questa pagina web è stato, come minimo in buona parte, inglobato in quest’altro sito:

http://isocpp.org/faq


Purtroppo queste due risorse sono entrambe in inglese, ma la loro utilità, per chi è interessato a una spiegazione esaustiva e molto chiara su tanti argomenti, è indiscutibile. Comunque sia, uno degli argomenti trattati su questo sito è: «Dovrei o no usare l’istruzione using namespace std nel mio codice?» E la risposta è: «Probabilmente no.»

La trovate qui:

http://isocpp.org/wiki/faq/coding-standards#using-namespace-std


A questo punto potete capire quanto mi senta in colpa ad avervela fatta usare così tanto, ma la realtà è che, per programmi piccoli come i nostri, questa istruzione è del tutto innocua. Adesso però possiamo riuscire a sbarazzarcene.

Dobbiamo però capire cos’è un namespace.


Anche in questo caso, la risposta alla domanda è una diretta conseguenza del dubbio che assale molti di fronte alle tante sfaccettature del linguaggio C++: perché mai dovremmo spendere tanta fatica a imparare un singolo linguaggio di programmazione?

In effetti la risposta che mi viene è sempre la stessa: è un linguaggio che presenta un sacco di strumenti utili a chi vuole lavorare in squadra. La possibilità di collaborare in tanti a un unico progetto senza pestarsi i calli è un vantaggio non da poco quando si devono scrivere programmi molto ricchi e complessi.

I namespace fanno parte di quei preziosi strumenti di collaborazione.


Abbiamo visto che in linea generale non dobbiamo preoccuparci molto dei nomi che diamo alle variabili, tant’è che nella maggior parte dei programmi vediamo comparire con una prevedibilità più che rispettabile un ristretto insieme di nomi: ‘i’ per le variabili di tipo int, ‘l’ per quelle long, ‘d’ e ‘f’ per due tipi che ancora non abbiamo visto, i double e i float, ‘u’ per gli unsigned int e ‘s’ per string o similari. Quasi con la stessa frequenza troviamo ‘v’ per vector e ‘counter’ per una variabile che viene usata per registrare la quantità di una qualche cosa.

Il vantaggio dei nomi di una sola lettera è che sono ovviamente molto veloci da scrivere, perciò non fanno perdere tempo quando si deve buttare giù il codice. Nonostante la stragrande maggioranza dei programmatori abusi di questi nomi, il C++ garantisce che il nome scelto da uno dei collaboratori di un progetto non interferisca con quello scelto da un altro grazie al principio dello scope: se una variabile è locale a un pezzo di codice, il suo nome non crea ambiguità con quello contenuto in altri pezzi di codice.

Il che vuol dire: finché io mi occupo delle mie funzioni e delle mie classi, non creo disturbo a te che stai usando gli stessi ‘i’, ‘u’, ‘s’ e così via nelle tue funzioni e nelle tue classi.


Ho specificato funzioni e classi perché il discorso può cambiare se prendiamo come esempio altre parti del codice. Cosa succede per esempio quando uno di noi si trova nella necessità di dichiarare una variabile globale?


Se ve ne ricordate, anche in questo caso come regola generale potremmo anche non essere a conoscenza del fatto. Infatti anche in questa situazione il C++, tramite il mascheramento dei nomi di variabile, ci consente di lavorare sulle nostre variabili senza interferire con quelle globali.


Nota: vi ricordate cos’è il maschermento, vero? Se esiste una variabile globale che si chiama ‘valore Importante’, ma all’interno di una funzione io creo un’altra variabile

int valoreImportante;

d’ora innanzi, quando userò il nome ‘valoreImportante’ il compilatore farà riferimento alla variabile locale, non a quella globale.

Se voglio fare riferimento a quella globale devo usare il simbolo ‘::’ in questo modo:

::valoreImportante = 5 // la variabile *globale* valoreImportante
                         // è stata portata a 5

Fine della nota.


Tutto parrebbe sotto controllo, quindi, ma il fatto che ne parliamo già vi sta facendo sospettare che qualcosa che potrebbe non funzionare come un orologio svizzero potrebbe saltare fuori…

E avete ragione ;-)


Mettiamo che a un certo punto io, non sapendo che voi avevate già scritto la class Robot, decida di scrivere un codice come questo:

class Robot{
    …
 }

oppure come questo:

struct Robot{
    …
 }

Vi ricordate cos’è una struct, vero? È come una class, ma i membri sono pubblici di default.

Cosa succederebbe secondo voi in questo caso?

Purtroppo le regole del mascheramento in questa situazione potrebbero non essere applicabili. Infatti chi scrive un blocco di codice lungo e complicato come quello di una class di solito lo fa perché ha bisogno di usarlo come stampo per creare più oggetti, qua e là nel codice, perciò vuole che il nome della classe sia visibile un po’ ovunque – abbia cioè uno scope globale.


Questo può facilmente diventare un problema in quei software sviluppati da grandi aziende dove lavorano decine o centinaia di programmatori, la maggior parte dei quali, come dicevamo, tende a usare nomi semplici che si ricordano e si digitano con facilità – e di conseguenza sono spesso gli stessi.


Ma anche noi potremmo trovarci nei problemi!

Vi può sembrare un’eventualità un po’ remota, visto che fin’ora siamo riusciti a scrivere solo un programmino con una singola class, ma non avete considerato un altro aspetto: all’inizio dei nostri programmi inseriamo sempre la scritta

#include <iostream>

Dopo quella riga, tutto ciò che è contenuto nel file “iostream” viene copiato nel nostro codice.

Siamo sicuri di essere a conoscenza di tutti i nomi che sono contenuti lì, in modo da non duplicarli?

E se includiamo “vector”? E “string”? Oppure qualcuno degli altri, per i quali, se vi interessa, posso fornire una lista:


<cassert> <array> <atomic> <algorithm>
<cctype> <bitset> <condition_variable> <chrono>
<cerrno> <deque> <future> <codecvt>
<cfenv> <forward_list> <mutex> <complex>
<cfloat> <list> <thread> <exception>
<cinttypes> <map> <functional>
<ciso646> <queue> <initializer_list>
<climits> <set> <iterator>
<clocale> <stack> <limits>
<cmath> <unordered_map> <locale>
<csetjmp> <unordered_set> <memory>
<csignal> <vector> <new>
<cstdarg> <numeric>
<cstdbool> <random>
<cstddef> <ratio>
<cstdint> <regex>
<cstdio> <stdexcept>
<cstdlib> <string>
<cstring> <system_error>
<ctgmath> <tuple>
<ctime> <typeindex>
<cuchar> <typeinfo>
<cwchar> <type_traits>
<cwctype> <utility>
<valarray>


Che ne dite? C’è una qualche possibilità che uno dei nomi lì dichiarati possa entrare in conflitto con uno dichiarato da noi? Per quanto rara, la possibilità c’è. Ma il C++ offre una soluzione anche per questo, e la soluzione sono i namespace.


La sintassi dei namespace è semplice, ma ci interessa di più capirne il meccanismo.

In pratica un namespace non fa altro che dichiarare un nuovo nome. Tutto ciò che creiamo da quel momento in poi, se vogliamo possiamo includerlo in quel nome. In questa maniera, per far riferimento a ciò che abbiamo creato, dovremo dare due nomi e non più uno solo: il nome del namespace e quello del nostro blocco di codice.


Un esempio renderà tutto più chiaro.

Mettiamo il caso che stiamo tutti lavorando sullo stesso progetto. Siamo autorizzati a dichiarare classi, funzioni e quant’altro a nostro piacimento. Per non interferire l’uno con il lavoro dell’altro, scegliamo di assegnarci un namespace ciascuno, partendo dai rispettivi nomi e cognomi.

Il nostro collega Stanislao Moulinsky sceglie per sé il namespace “stan”.

A questo punto, se la classe Robot l’avesse creata lui, avrebbe dovuto dichiararla così:

namespace stan{
    class Robot{
       … // tutto il codice della classe
    };
 }

Come vedete non c’è niente di scioccante: la nostra classe viene semplicemente ‘inglobata’ in un ulteriore copia di parentesi graffe; prima di questa parentesi viene scritta la parola namespace e un’altra parola a nostra scelta che sarà il nome del nostro namespace.


A questo punto il nome della class “Robot” è sparito: non è più visibile dal resto del codice. L’unica maniera per arrivarci è dire al compilatore: «La class “Robot” che è stata dichiarata all’interno del namespace “stan”.»

Il simbolo per “entrare dentro” un namespace lo abbiamo già visto in un’altra occasione: è il simbolo di scope resolution ‘::’.

Quindi per arrivare a “Robot” d’ora in avanti si dovrà scrivere

stan::Robot

Vediamo un po’ come funziona nella pratica.

La nostra istruzione:

Robot rbt("Mario");

dovrà diventare:

stan::Robot rbt("Mario");

mentre

int muoviRobot(Robot &rbt)

ora sarà:

int muoviRobot(stan::Robot &rbt)

Potete provare ad apportare queste piccole modifiche e a verificare che il programma funzioni come prima.

In sostanza ciò che abbiamo ottenuto è che, se anche Goffredo avesse creato una class “Robot”, ma l’avesse inglobata in un namespace “goff”, o con qualsiasi altro nome, non correremmo il rischio di creare un oggetto partendo dallo stampo sbagliato.

O meglio: il programma compilerebbe lo stesso. Infatti non si arriva mai a domandarsi se ci siano due classi con lo stesso nome perché, o almeno una delle due è stata inglobata in un namespace, oppure il compilatore si rifiuta di proseguire. La regola è che non si può usare due volte lo stesso nome nello stesso spazio di visibilità, di qualsiasi cosa stiamo parlando.


Quando sono state progettate le librerie standard, ossia quelle che vengono fornite insieme a ogni compilatore, come a esempio i file “iostream” e “vector” che abbiamo già usato, si è cercato di eliminare la possibilità di conflitti fra i vari nomi.

Si è perciò studiato un modo per fare in modo che i programmatori potessero usare i nomi che volevano per le loro classi e funzioni senza doversi preoccupare di eventuali conflitti nel momento in cui includevano i files della libreria standard. Per ottenere questo risultato, tutti i nomi contenuti nelle librerie standard sono stati inglobati nel namespace “std”.


Ciò significa che né vector, né string, né cout né nessun altro degli oggetti che abbiamo già usato sarebbe di per sé visibile dal compilatore semplicemente a seguito dell’istruzione #include <fileDellaLibreriaStandard>, bensì ciascuno di essi avrebbe dovuto essere preceduto dall’istruzione std::

std::vector miovector;
 std::cout << "Testo a schermo" << std::endl;
 std::string testo = "Testo a caso";

Questa sintassi è la più sicura, nel senso che è quella con cui si corre il rischio di commettere meno errori. Ha però uno svantaggio: è un po’ scomoda!

Noi perciò abbiamo deciso di liberarci del problema sfruttando l’istruzione using, la quale introduce in modo permanente nello psazio di visibilità in cui ci si trova dei nomi che di per sé si troverebbero nascosti dentro un namespace. Quando abbiamo scritto

using namespace std;

''

abbiamo detto al compilatore di includere l’intero namespace “std” nel nostro programma, ossia di darci libero accesso ai nomi contenuti in quel namespace senza che dovessimo specificare std::

Attenzione! Questo non significa che sono state incluse le istruzioni relative a quei nomi, ossia tutte le righe di codice che fanno funzionare a esempio un vector, perché quello è compito dell’istruzione #include, ma solo che abbiamo potuto accedere senza limitazioni ai nomi.


Se pensate che siamo stati molto furbi, non mi trovate d’accordo. Siamo al contrario stati poco previdenti. Infatti ci saremmo dovuti garantire di conoscere a menadito le librerie standard prima di fare in modo che il nostro codice potesse entrare in conflitto con uno dei nomi lì esistenti.

A dire il vero il rischio era basso finché abbiamo continuato a scegliere, per le nostre funzioni e variabili, dei nomi in italiano: infatti non esiste alcuna parola italiana fra i nomi delle librerie standard!

Adesso però, venuti a conoscenza del pericolo e saputo che alcuni tra i massimi esperti mondiali di C++ disapprovano la nostra scelta, non possiamo proseguire così.


Questo significa forse che dovremo cominciare a scrivere sempre std::string, std::cout e così via? Beh, la risposta sincera sarebbe che i bravi bambini farebbero così.

Per quelli un po’ discoli come sono sempre stato io :-) esiste una sintassi di compromesso:

using singolo_nome_che_si_vuole_usare_nel_programma;

In pratica si può elencare, all’inizio del nostro codice, i nomi che vogliamo usare, ma che sono nascosti in dei namespace, e il compilatore ci farà accedere a quei nomi lasciando però ‘nascosti’ gli altri.

Ossia, dopo che ho scritto una volta:

using std::cout;
 using std::endl;
 using std::string;

da quel punto in poi potremo usare cout, endl e string come se avessimo scritto using namespace std. Conviene quindi scriverlo all’inizio del programma:

#include <iostream>

 using std::cin;
 using std::cout;
 using std::endl;
 using std::string;

 namespace stan{
    class Robot{
       string nome;
       int carica;
       int x;
       int y;

    public:
       Robot(string pa_nome)
       {
          nome = pa_nome;
          x = 0;
          y = 0;
          carica = 100;
       }

       void setNome(string battezza) {nome = battezza;}

       string getNome() { return nome; }

       void vaiSu()
       {
          y += 1;
          carica -= 5;
       }

       void vaiGiu()
       {
          y -= 1;
          carica -= 5;
       }

       void vaiSx()
       {
          x -= 1;
          carica -= 5;
       }

       void vaiDx()
       {
          x += 1;
          carica -= 5;
       }

       void datiASchermo()
       {
          cout << "Robottino '" << nome << "':" << endl;
          cout << "   posizione: x = " << x << ", y = " << y << endl;
          cout << "   carica residua: " << carica << "%" << endl;
          cout << endl << endl;
       }

       ~Robot()
       {
          cout << endl << endl;
          cout << "Torna presto a trovarci!" << endl << endl;
       }
    };
 }

 int muoviRobot(stan::Robot &rbt);

 int main()
 {
    stan::Robot rbt("Mario");
    // rbt.setNome("Mario");

    while(muoviRobot(rbt))
       rbt.datiASchermo();

    return 0;
 }

 int muoviRobot(stan::Robot &rbt)
 {
    string testo;
    cout << "Direzione? ";
    cin >> testo;

    if("a" == testo) {
       rbt.vaiSx();
       return 1;
    } else if ("w" == testo) {
       rbt.vaiSu();
       return 1;
    } else if ("s" == testo) {
       rbt.vaiDx();
       return 1;
    } else if ("z" == testo) {
       rbt.vaiGiu();
       return 1;
    } else if ("0" == testo) {
       return 0;
    }
    return 2; // 2 == tasto non riconosciuto
 }

Questa soluzione non è sicura come la sintassi raccomandata, ma per i nostri scopi è più che sufficiente. Possiamo quindi dare l’addio a using namespace std e proseguire verso altri lidi.


Is this important?

Questo è l’ultimo capitolo un pochino concettoso di questa puntata del tutorial; sia quello precedente che quelli a seguire riguardano più che altro miglioramenti al modo di approcciarsi al codice. Qui invece siamo di nuovo con le mani in pasta tra puntatori e indirizzi di memoria.


Abbiamo visto la parola this comparire nel JS, ma quando abbiamo cercato di scrivere un codice equivalente nel C++, essa si è volatilizzata.

In effetti nel C++ ha una funzione molto più specifica di quanto non abbia nel JS, perciò non sempre è obbligatoria. È però vero che ha un buon numero di ‘cultori’ i quali preferiscono usarla anche quando non è obbligatoria.

Vediamo di capire meglio di cosa si tratta.


Per scoprire cosa sia this abbiamo bisogno di una class qualsiasi, anche semplicissima. Chi non volesse perdere il codice di “Robottino” può chiudere il progetto che abbiamo usato fin qui e riaprire il buon vecchio “Prove”. L’idea non è malvagia perché nel prossimo capitolo ci servirà di nuovo “Robottino”, perciò ci può far comodo lasciarlo com’è.


Per chiudere il progetto, come prima cosa chiudiamo i file aperti facendo click sulla X in alto a destra (chi usa Qt in un sistema operativo diverso da Kubuntu potrebbe trovarsi con delle finestre leggermente diverse, ma con un po’ di spirito di osservazione dovrebbe riuscire a ottenere comunque il risultato).


Qt_IV_12.png


Quando tutti i file sono chiusi, basta fare click destro sulla parola “Robottino”…


Qt_IV_13.png


…e scegliere Close project "Robottino".


Qt_IV_14.png


Per riaprire un progetto esistente, tornare alla schermata iniziale facendo click su “Welcome”.


Qt_IV_15.png


E selezionare il progetto che interessa.


Qt_IV_16.png


Come al solito, il nostro codice andrà nel file main.cpp.


What is this?

Creiamo la class più piccola possibile:

#include <iostream>

 class Qualunque {};

 int main()
 {
    Qualunque mioOggetto;
 }

Abbiamo creato una class vuota. Meno di così, onestamente, non potevamo scrivere. A dire il vero potevamo evitare l’istruzione

#include <iostream>

ma tanto ora ci servirà, perciò…

Adesso lasciamoci prendere dalla curiosità: in quale cella di memoria sarà stata inserita la nostra istanza di classe ‘mioOggetto’? Proviamo a scoprirlo:

#include <iostream>

 using std::cout;
 using std::endl;

 class Qualunque {
 };

 int main()
 {
    Qualunque mioOggetto;
    cout << "Indirizzo di memoria di mioOggetto: "
         << (unsigned long) &mioOggetto << endl << endl;
 }

Vi ricordate il discorso di (unsigned long), vero? Serve per fare in modo che std::cout gestisca il numero che rappresenta l’indirizzo di memoria di mioOggetto nella maniera che piace a noi, ossia come un intero positivo.

Adesso andiamo a curiosare su cosa sia questo this.

class Qualunque {
 public:
    Qualunque() { cout << (unsigned long) this << endl; }
 };

Eh, eh, eh! Questa non ve la aspettavate, dite la verità.

Andiamo con calma. Prima informazione: this è un membro della class. Con questo intendo dire che è un membro di ogni class. Esiste nelle class senza bisogno che noi lo dichiariamo.

Detto in altre parole: ogni class possiede un membro nascosto il cui nome è this.


Seconda informazione: se vi state chiedendo se somiglia di più a una proprietà o a un metodo, vi dico che si comporta né più né meno come una proprietà. Infatti this è definito come un puntatore.

Terza informazione: se fate girare il programma, ci arrivate da soli. Questo è l’output:


Qt_IV_17.png


La terza informazione è quindi: this è un puntatore che tiene di mira l’indirizzo di memoria dell’oggetto, ossia dell’istanza di class che abbiamo creato.


Vi si sta surriscaldando il cervello? In effetti, la prima volta che ho visto this mi sono domandato se avessi capito bene la spiegazione. A cosa diavolo potrebbe mai servire un puntatore, contenuto in un oggetto, che tiene di mira l’indirizzo di memoria dell’oggetto stesso?


In effetti non è facile da cogliere, ma come ci è già successo dobbiamo scindere le due parti del problema: una cosa è capire che cos’è e una cosa è capire a cosa serve.

Ora come ora a noi serve capire cos’è, e in fondo non si tratta di niente di complicato. Ci serve capire cos’è perché ci potrà capitare di incontrarlo lavorando con le librerie Qt, ma sarà molto difficile che ci troviamo costretti a usarlo.

Però mi rendo conto –anche perché sono fra queste– che esistono persone che non riescono a penetrare bene un argomento se non trovano il modo di applicarlo nella pratica. Per facilitare la vita a queste persone proveremo a fare un breve discorso generale sull’uso di this, ma nel caso vi rimanesse oscuro, non vi preoccupate: l’importante è che, quando lo troviamo in qualche programma, sappiamo che cos’è, altrimenti il codice ci rimarrebbe oscuro.


A cosa serve this?

Un sacco di persone usano this per uno scopo che non è quello per cui è nato, cioè lo adoperano per rendere più leggibile il codice.

Questo scopo è il più semplice da capire, per cui lo possiamo trattare per primo.

Cerchiamo di ricordarci cosa abbiamo detto poco fa: un sacco di programmatori si annoiano a scrivere lunghi nomi per le variabili, perciò cercano di essere il più stringati possibile. Ecco che, nel corso degli anni, si è creata una consuetudine molto abusata:

  • i per una variabile di tipo int;
  • l per una variabile di tipo long;
  • d per una variabile di tipo double (non le conosciamo, ma esistono);
  • f per una variabile di tipo float (anche queste non le conosciamo);
  • s per un oggetto string (o char* → si usano molto nel C, ma molto meno nel C++, quindi può darsi che non le useremo mai!);
  • v per un oggetto vector;
  • c per una variabile di tipo char (va bene, nemmeno questo l’abbiamo mai visto, ma non ne sentiamo la mancanza. Attenzione! È scritto minuscolo: ‘c’. La corrispondente maiuscola si ritrova nell’istruzione “extern C”, che è tutta un’altra cosa);

e magari anche altri che ora non mi vengono in mente. Negli esempi si trova spesso scritto f() per indicare una funzione, ma nel codice reale non credo nessuno lo usi.

La conseguenza è che i programmi sono pieni di ‘i’, ‘v’, ‘s’… Nonostante il mascheramento, se non si sta attenti si corre il rischio di ripetere i nomi.


Non capita di rado di vedere usare questi nomi così brevi anche per le proprietà delle class:

class Qualunque {
 private:
    int i;
 …

Magari a un certo punto ci si rende conto che il valore di quella variabile deve essere impostato dall’utente; a questo punto si scrive un metodo che accetta un parametro il cui valore viene copiato nella proprietà privata ‘i’. Indovinate un po’, in forza dell’abitudine, come viene chiamato l’argomento di quella funzione?

Ma che bravi, avete proprio indovinato: sempre ‘i’.

Ecco che ci troviamo di fronte a qualcosa del genere:

void setValI(int i) {  i = i; }

Carino, vero? La prima domanda che scappa è: «ma funzionerà?»

Un po’ sadicamente vi lascio con la curiosità di provare: l’esercizio non è per nulla al di là delle vostre possibilità. In pratica dovete verificare se la proprietà privata ‘i’ viene valorizzata con la copia del valore dell’argomento ‘i’ oppure no. O ancora, se il compilatore vi dà errore.

Noi invece proseguiamo con quella che è una possibile soluzione al problema (posto che non si voglia o possa rinominare una delle variabili in gioco!): l’uso di this.

void setValI(int i) {  this->i = i; }

Ebbene sì, essendo this un puntatore, va usata la sintassi ‘->’.

In questo caso l’ambiguità si risolve perché la scritta this->i garantisce che in questo caso il compilatore interpreti ‘i’ come la proprietà ‘i’ e non come l’argomento ‘i’ del metodo setValI().


Riuscite a capire come mai? Proviamo a guardarlo insieme.

Poniamo il caso che in main() venga dichiarato un puntatore che sia valorizzato con l’indirizzo dell’oggetto mioOggetto:

Qualunque* p_mioOggetto = &mioOggetto;

Adesso facciamo diventare pubblica la proprietà ‘i’ e rendiamo un po’ più chiaro il codice dentro il costruttore:

class Qualunque {
 public:
    int i;
    Qualunque()
    {
       cout << "Valore di this = " 
            << (unsigned long) this << endl;
    }
 };

A questo punto ‘i’ è visibile dall’esterno. Significa che in main() possiamo scrivere:

   p_mioOggetto->i = 8; // un numero a caso

Così facendo modifichiamo direttamente ‘i’. Adesso proviamo a fare altrettanto dentro il costruttore tramite this:

this->i = 12; // un numero a caso

In entrambi i casi possiamo aggiungere un’istruzione che mostri il risultato delle nostre fatiche:

cout << "Valore di i = " << this->i << endl;

Facciamo un’ultima piccola modifica al codice che ci consenta di ottenere il seguente risultato complessivo:

#include <iostream>

 using std::cout;
 using std::endl;

 class Qualunque {
 public:
    int i;
    Qualunque()
    {
       cout << "Valore di this = "
            << (unsigned long) this << endl;
       this->i = 12; // un numero a caso
       cout << "Valore di i = " << this->i << endl;
    }
 };

 int main()
 {
    Qualunque mioOggetto;

    Qualunque* p_mioOggetto = &mioOggetto;

    p_mioOggetto->i = 8; // un numero a caso

    cout << "Valore di p_mioOggetto = "
         << (unsigned long) p_mioOggetto << endl;

    cout << "Valore di i = "
         << p_mioOggetto->i << endl << endl;
 }

Il risultato che otteniamo dovrebbe parlare da solo:


Qt_IV_18.png


Possiamo quindi confermare che this è l’equivalente di un puntatore al nostro oggetto che sia stato dichiarato in una funzione esterna alla classe; la differenza è che this è un membro della classe, perciò può accedere alla proprietà ‘i’ anche quando è private (mentre ‘p_mioOggetto’ non potrebbe farlo).


Proseguendo il ragionamento, come un puntatore esterno alla classe può accedere ai membri pubblici dell’oggetto tramite ->, lo stesso può fare this.

Ma potrebbe un puntatore come ‘p_mioOggetto’ accedere a una variabile locale a una funzione della classe? Ovviamente no. E a un argomento della stessa funzione? Sempre no, perché abbiamo già detto che la distinzione fra un argomento e una variabile locale è solo nella sintassi e nel fatto che gli argomenti ricevono i valori dei parametri; per tutto il resto, un parametro è indistinguibile da una variabile locale.


In conseguenza di ciò, tornando all’esempio di prima, quando scriviamo this->i siamo sicuri che il compilatore interpreta ‘i’ come proprietà della classe e non come argomento del metodo setValI() perché this non può vedere tale variabile.

Non ci resta che provare:

#include <iostream>

 using std::cout;
 using std::endl;

 class Qualunque {
 private:
    int i;
 public:
    Qualunque() {}
    ~Qualunque() { cout << "valore di i = " << i << endl << endl; }
    void setValI(int i) {  this->i = i; }
 };

 int main()
 {
    Qualunque mioOggetto;

    mioOggetto.setValI(8);

    // All'uscita da main() vengono cancellate tutte
    // le variabili, pertanto anche l'oggetto mioOggetto.
    // Prima di rimuoverlo dalla memoria, viene invocato
    // il distruttore.
 }

Usando il distruttore ci siamo risparmiati di dover scrivere un metodo per mostrare a schermo il valore della variabile privata ‘i’. Se fate girare il programma, vi confermerà che this consente al compilatore di scegliere la variabile giusta.


Qualcuno di voi però si starà chiedendo: «Ma era necessario perdere così tanto tempo dietro a questo esempio? Sarebbe bastato chiamare le due variabili in modo diverso –per dire, ‘i1’ per la proprietà della classe e ‘i2’ per l’argomento del metodo– che tutto sarebbe filato liscio senza tanti puntatori e complicazioni varie…»

L’osservazione è verissima. Però conviene sapere che this può essere utile per cavarsi d’impaccio in certe situazioni, e una situazione può essere: quando si suppone che il compilatore possa sbagliare a scegliere una variabile. Ora cercheremo di essere più chiari, ma per adesso, riallacciandoci all’inizio del discorso, forse siamo riusciti a far apparire più chiaro il motivo per cui this ha così tanti estimatori.

Alcuni programmatori infatti ritengono che si debba mettere this-> di fronte a ogni proprietà di una class, giacché così, sostengono, chi legge riconosce immediatamente le variabili membro della class da quelle locali. In questo modo verrebbe migliorata la leggibilità del codice.


Entrare nella diatriba se sia una buona idea o no sarebbe un suicidio. Forse non ci crederete, ma in tutti i forum sul C++ prima o poi salta fuori qualcuno che ventila un metodo per scegliere i nomi per le proprietà delle class.

  • Ci sono i sostenitori dello “m_” che sostengono che le proprietà dovrebbero tutte iniziare così, per m_. Quindi la nostra variabile ‘i’ dovrebbe diventare ‘m_i’ e, tornando a “Robot”, ‘nome’ dovrebbe girarsi in ‘m_nome’, ‘carica’ in ‘m_carica’ e così via.
  • Altri invece sono sostenitori della lineetta in basso (underscore) finale: ‘_’. Con questo metodo avremmo invece: i_, nome_, carica_…
  • Alcuni accettano il consiglio, ma spostano lo underscore all’inizio della parola: _i, _nome, _carica …
  • Altri si sposano con this-> e gli rimangono fedeli fino alla fine (this->i, this->nome, this-> carica…).
  • Altri ancora avanzano altre proposte.

Da parte nostra ci facciamo baluardo del succitato sito della Standard C++ Foundation, il quale entra con delicatezza nell’argomento alla FAQ “Which is better: identifier names that_look_like_this or identifier names thatLookLikeThis?”

http://isocpp.org/wiki/faq/coding-standards#identifier-name-conventions

Il tema lì trattato è un po’ più generale di questo (si parla di tutti i nomi, non solo di quelli delle proprietà delle classi) e non voglio mettere in bocca agli autori cose che non hanno scritto. Secondo me però la conclusione è che a oggi non è stato creato un vero e proprio standard su come scegliere i nomi delle proprietà delle classi, pertanto un metodo può valere l’altro.

Anche solo il caro vecchio buonsenso può essere d’aiuto. Qualcuno potrebbe per esempio proporre di evitare di usare nomi estremamente brevi per le proprietà, o comunque di non basarsi solo su quelli di una lettera.


Ci tengo però a far notare che, seppure il C++ non sia il C, e anzi se ne stia distanziando sempre di più, ne è comunque uno dei figli.

Se riprendiamo in mano il testo di Kernighan e Ritchie sul C, nel capitolo 2 (Types, operators, and Expressions), al paragrafo 2.1 (Variable Names) possiamo leggere: «Don’t begin variable names with underscore, however, since library routines often use such names.»

Nella seconda edizione del manuale sul C++ scritto da Stroustrup, di cui purtroppo non ho la versione originale inglese, al paragrafo 3.2 si legge: «I nomi che iniziano con un carattere di sottolineatura vengono generalmente riservate a particolari funzioni dell’ambiente esecutivo, perciò non è consigliabile l’uso di tali nomi in programmi applicativi.» L’errore nomi-riservate non è mio, ma si trova nella traduzione, che è pessima. Tuttavia il significato mi sembra chiaro.


Ma, sul serio, allora a cosa serve?

Nella stragrande maggioranza dei casi, se non mascheriamo i nomi delle proprietà della classe, non siamo obbligati a usare this.

Ci sono però un paio di casi in cui è indispensabile. Uno riguarda l’ereditarietà dei template e, nel caso ci dovesse interessare, ce ne occuperemo quando li incontreremo: fin qui non abbiamo parlato né dell'ereditarietà né tanto meno dei template, perciò non lo affronteremo.

Per chi non potesse farne a meno, segnalo l’unica pagina che ho trovato dove è spiegato nel dettaglio. La pagina è in inglese e tratta argomenti incomprensibili per coloro che conoscono del C++ solo quello di cui abbiamo parlato fin qui:

http://www.parashift.com/c++-faq-lite/nondependent-name-lookup-members.html


Un altro caso lo troviamo nel manuale sul C++ scritto dal prof. Stroustrup, ma mi sembra un po’ complicato. Penso sia meglio se ne propongo una versione semplificata.

L’esempio riguarda le cosiddette doubly linked lists, che forse potremmo tradurre con una certa libertà liste a catena. Proviamo ad approcciarle partendo dai vector.

Abbiamo visto che un vector è una specie di contenitore che ci consente di maneggiare gruppi di oggetti purché siano tutti dello stesso tipo. Il tipo degli oggetti va indicato con cura subito dopo la parola vector, fra parentesi angolari, per esempio vector<int>.

vector è molto flessibile, tant’è che potremmo usarlo anche per creare una sequenza degli oggetti progettati da noi; se volessimo, tanto per dire, usare cinque dei nostri robottini insieme, potremmo dichiarare una variabile in questa maniera:

vector<Robot> tantiRobot;

e poi aggiungerci dentro cinque “Robot”.


L’accesso agli oggetti contenuti in un vector è consentito tramite un indice. Nel nostro esempio, ‘tantiRobot.at(0)’ ci consentirebbe di usare il primo robottino, ‘tantiRobot.at(1)’ il secondo e così via.

Una lista di oggetti di questo genere ha come caratteristica il fatto che ogni oggetto ‘ignora’ la presenza degli altri. Poiché si può raggiungere ogni oggetto tramite un indice, nessuno degli elementi della lista viene influenzato dalla presenza degli altri.

Nel caso di ‘tantiRobot’, pensiamo a esempio al robottino numero 3. Poniamo il caso che l’abbiamo chiamato Lauretta. Immaginiamoci la sequenza in orizzontale, da destra verso sinistra. L’oggetto 3 avrebbe alla sua destra l’oggetto 2, che potremmo aver chiamato Terenzio, e alla sua sinistra l’oggetto 4, mettiamo Giada. Se il robottino 3 fosse cancellato, Terenzio si troverebbe al fianco di Giada, ma nessuno di loro verrebbe nemmeno a sapere di questo cambiamento: infatti nessun dato sarebbe cambiato né in Terenzio né in Giada.


Questa però non è l’unica maniera di concepire una lista di oggetti. Si può anche pensare che esistano liste in cui ogni oggetto conosce il ‘nome’ dell’oggetto che ha al fianco e per raggiungere un determinato oggetto non si può usare un indice, ma si deve per forza attraversare la lista.

Avete mai partecipato a una caccia al tesoro? Sapete come funziona il gioco?

Le regole sono molto semplici: un oggetto viene nascosto all’insaputa dei partecipanti. Il luogo dove si trova viene descritto in un indovinello, che viene a sua volta nascosto in un altro luogo.

Anche dove trovare questo indovinello, però, è svelato da un ulteriore indovinello, a sua volta celato in un nascondiglio diverso. E così via, quante volte a piacere, finché non si decide che gli indovinelli sono un numero sufficiente. L’ultimo di questi viene passato ai concorrenti, i quali a quel punto possono iniziare la caccia. Chi arriva primo si tiene il ‘tesoro’.


Dal punto di vista di chi nasconde, quello che viene consegnato ai concorrenti è l’ultimo indovinello, mentre dal punto di vista di chi partecipa al gioco è il primo, ma questo non cambia la sostanza: l’unica maniera per arrivare al tesoro è passare da un indovinello all’altro. Se uno degli indovinelli venisse a mancare, perché rovinato dall’umidità, portato via dal vento o che so io, non ci sarebbe modo di giungere all’oggetto successivo.

Lo stesso tipo di considerazioni si potrebbe fare per una catena: gli anelli sono tutti connessi fra di loro e la catena rimane tale finché nessuno di essi si rompe; da quel momento, gli oggetti alla destra dell’anello spezzato non avrebbero più alcun legame con quelli alla sinistra.


Se quindi volessimo simulare in un programma un gioco di caccia al tesoro, potremmo farlo tramite un vector, ma forse non sarebbe la scelta migliore perché in teoria non dovremmo trovarci nelle condizioni di dover usare l’indice. Forse dovremmo preferirgli una doubly linked list, ossia un contenitore in cui ogni oggetto contiene un riferimento, o una connessione, a quello che lo segue (e anche uno a quello che precede).


Anche se fino a ora non ne avevamo parlato, sappiate che non sono per niente oggetti rari. Anzi! Se guardate i nomi dei file della libreria standard che abbiamo elencato sopra, troverete “list”. Beh, l’istruzione

#include <list>

rappresenta il primo passo per poter usare un oggetto list, che è molto simile a un vector, ma con la differenza che è progettato con il meccanismo della connessione tra gli elementi contenuti (ossia è una doubly linked list).


Prendendomi la licenza di ritoccare l’esempio del prof. Stroustrup per renderlo più semplice, proverò a far vedere qual è il punto nodale di una lista a catena e in cosa ci può dare una mano this.

Una catena è composta di singoli anelli collegati fra di loro.

Possiamo quindi provare a figurarci una class Anello che ci aiuti a riprodurre questa situazione.

class Anello {
     …
 };

Una serie di anelli forma una catena, ma l’idea va considerata nel modo più ampio possibile. In una caccia al tesoro ogni “Anello” potrebbe rappresentare un indovinello.

Quando andremo a usare la nostra class Anello, dovremo definire un’istanza per ogni Anello che ci servirà. Invece adesso, in fase di progettazione della class, dovremo pensare ad un modo in cui ogni Anello possa essere ‘agganciato’ al successivo.


Per collegare un anello al successivo la cosa più semplice è fornire un puntatore che tenga di mira l’Anello successivo. Se deve tenere di mira un Anello, ovviamente non può che essere un puntatore di tipo “Anello”:

class Anello {
 private:
     Anello* successivo;
 };

Quest’istruzione può lasciare qualcuno un po’ interdetto. Come, all’interno di una class stiamo creando un’istanza della medesima class?!

Prima di tutto, attenzione! Non stiamo creando un’istanza della class Anello, bensì un puntatore di tipo Anello.

Le due cose sono ben diverse. Come ricorderete un puntatore non è altro che una variabile che contiene un numero intero; è il fatto che quel numero rappresenti un indirizzo di memoria che la rende così interessante!

Il puntatore comunque non deve obbligatoriamente essere valorizzato con l’indirizzo di memoria di qualcosa fin da subito. Intanto può cominciare a esistere, seppure avendo in sé un numero casuale che ci porterebbe chissà dove.


Per il compilatore tutto ciò non è un problema. Nel momento in cui dichiariamo il nostro puntatore ad Anello, lui già sa che cos’è “Anello”: è una class. L’ha letto poco sopra. Per cui per lui non ci sono problemi: il puntatore può esistere.


Teniamo anche conto di un altro fatto: tutto ciò che scriviamo mentre progettiamo una class rimane a livello di puro, come dire, design finché non creiamo un’istanza di quella class. È l’oggetto, ossia l’istanza della class, che finisce in memoria e quindi riceve un indirizzo, non la descrizione, che è solo uno stampo. Per cui per il compilatore sapere che in una istanza di una class è contenuto un puntatore che deve tenere di mira un’altra diversa istanza della stessa class non è una cosa che crei problemi.


Abbiamo creato il primo passaggio della nostra class. Adesso ci dobbiamo aggiungere una connessione all’Anello precedente:

class Anello {
 private:
     Anello* successivo;
     Anello* precedente;
 };

Bene. Adesso ci serve un metodo che faccia funzionare questo codice, ossia che inserisca i valori giusti in ‘precedente’ e ‘successivo’.

Prima di scriverlo cerchiamo di vedere come potrebbero procedere le cose in una funzione che voglia servirsi di questa class:

int main()
 {
    Anello primo;
 }

Vogliamo creare una catena, sicché un “Anello” solo non ci basta:

int main()
 {
    Anello primo, secondo;
 }

Adesso vogliamo ‘collegare’ il primo “Anello” con il secondo. Ci farebbe comodo un metodo che accetti un “Anello” come parametro e che proceda a questa connessione; un metodo che ci consenta di scrivere qualcosa del tipo:

int main()
 {
    Anello primo, secondo;

    primo.connetti(&secondo);
 }

Vediamo se riusciamo a mettere insieme questo metodo:

void connetti(Anello* daCollegare)
 {
    // la proprietà 'successivo' deve puntare a daCollegare:
    successivo = daCollegare;
 }

Beh, non è stato così poi così difficile :-)

La domanda potrebbe essere: «Già, ma come diciamo poi a ‘secondo’ che il suo predecessore è ‘primo’?»

La prima soluzione che potrebbe venire in mente potrebbe essere di creare un metodo equivalente, sonoConnessoA(), che imposti il ‘precedente’ per ogni “Anello”.

void sonoConnessoA(Anello* daCollegare)
 {
    precedente = daCollegare;
 }

Questo ci porterebbe a dover aggiungere un altro rigo a main():

int main()
 {
    Anello primo, secondo;

    primo.connetti(&secondo);
    secondo.sonoConnessoA(&primo);

}

Vi sembra una buona soluzione?

Vi rispondo io: funziona, ma obbliga a troppi passaggi, il ché significa che induce a commettere errori. Vediamo di fare meglio.

Abbiamo già una funzione connetti(). L’ideale sarebbe se questo metodo riuscisse a fare entrambe le cose, ossia connettere un “Anello” al successivo e poi istruire il successivo che il suo precedente è l’oggetto i cui metodi stiamo invocando in questo momento.

Ma istruire l’oggetto successivo non è impossibile: infatti abbiamo un puntatore che lo tiene di mira (‘successivo’)!

Quel che dobbiamo fare è raggiungere la proprietà ‘precedente’ dell’oggetto tenuto di mira e dirgli: «ehi! Il tuo ‘precedente’ sono io!».

La prima parte è semplice, giacché

successivo->precedente

ci consente di arrivare proprio lì, alla proprietà ‘precedente’ dell’oggetto tenuto di mira da ‘successivo’. Il problema è la seconda parte dell’istruzione.

successivo->precedente = ??? ;

‘precedente’ è un puntatore, perciò quello che ci serve è l’indirizzo di memoria dell’oggetto in cui siamo in questo momento…


Già, è proprio così: anche questo dato ce l’abbiamo: è this!

Ecco la soluzione:

#include <iostream>

 class Anello {
 private:
    Anello* successivo;
    Anello* precedente;

 public:
    void connetti(Anello* daCollegare)
    {
       // la proprietà 'successivo' deve puntare a daCollegare:
       successivo = daCollegare;

       // la proprietà 'precedente' dell'Anello daCollegare
       // deve puntare a questo oggetto!
       successivo->precedente = this;
    }
 };

 int main()
 {
    Anello primo, secondo;

    primo.connetti(&secondo);
 }

L’istruzione #include <iostream> sarebbe inutile, ma tanta è l’abitudine…


Questo è uno dei pochi casi in cui non si potrebbe ottenere il risultato senza this, a meno che non si volesse scrivere un codice molto più complesso e ‘fragile’, nel senso di ‘con maggiori difetti’.


Questo aspetto della maggiore o minore fragilità del codice risulterà più chiaro analizzando l’esempio originario di Stroustrup, il quale affronta anche i problemi collaterali (ossia: qual è il precedente del primo “Anello”? E il successivo dell’ultimo?).

Lo riporto qui per intero, compresi i commenti originali dell’autore. Se però risultasse ostico, non vi preoccupate perché il testo di Stroustrup non è un aiuto per chi comincia da zero, come vuol essere questo, ma un punto di riferimento anche per chi è già un esperto.


Codice dal libro The C++ Programming Language – Second Edition di Bjarne Stroustrup:</pre>

class dlink {

    dlink* pre;  // previous
  dlink* suc;  // next
public:
  void append(dlink*);
  // ...
};
void dlink::append(dlink* p)
{
  p->suc = suc;   // that is p->suc = this->suc
  p->pre = this;  // explicit use of "this"
  suc->pre = p;   // that is, this->suc->pre = p
  suc = p;        // that is, this-> suc = p
}
dlink* list_head;
void f(dlink* a, dlink* b)
{
  // ...
  list_head->append(a);
  list_head->append(b);
}</pre>

Come vedete il prof. Stroustrup adotta una convenzione molto utile nello scrivere la class, ossia quella al rigo:

void dlink::append(dlink* p)

Questo ci introduce direttamente all’argomento del prossimo capitolo, ma sul momento, per poter leggere il codice con il sistema che abbiamo usato fin qui, fate finta che sia stato scritto in questo modo:

class dlink {
     dlink* pre;  // previous
     dlink* suc;  // next
 public:
     void append(dlink* p)
     {
        p->suc = suc;   // that is p->suc = this->suc
        p->pre = this;  // explicit use of "this"
        suc->pre = p;   // that is, this->suc->pre = p
        suc = p;        // that is, this-> suc = p
     }
     // ...
 };

 dlink* list_head;

 void f(dlink* a, dlink* b)
 {
     // ...
     list_head->append(a);
     list_head->append(b);
 }

Adesso che abbiamo un po’ cominciato a frequentare le class è arrivato il momento di scoprire perché abbiamo scritto tutto nel modo sbagliato :-)


Sorgenti e intestazioni: ogni cosa al suo posto

Questo mi auguro possa essere uno dei capitoli più rilassanti di questo tutorial. Non ci sono concetti da capire, infatti, ma solo un po’ di avvertenze per progettare in modo più ordinato.

Proviamo un attimo a fare mente locale su ciò che abbiamo visto fin qui.


Abbiamo scoperto che un programma in C++ è diviso in blocchi. Ci sono blocchi di diverso tipo, ma hanno in comune un grande vantaggio: ciò che succede al loro interno non influenza gli altri blocchi, se non per la parte che decidiamo noi tramite references e pointers. All’esterno di questi blocchi si trovano solo le istruzioni per il compilatore (es. #include), gli avvisi su ciò che si troverà in seguito (es. i namespace e i prototipi di funzione) e le dichiarazioni delle variabili globali.

Queste variabili globali sono, si può dire, l’unica ‘falla’ alla sicurezza del codice, perché in un grande progetto in cui lavorano centinaia di persone diventa molto difficile prevedere come saranno usate. Per fortuna il C++ offre un semplice rimedio anche per questa falla. Vediamo come funziona.


Quando siamo in tanti a lavorare sullo stesso progetto, ognuno lavora su un file diverso. È in fase di compilazione che i vari file vengono poi assemblati in un unico programma.

Bene, il fatto è che anche ogni file rappresenta uno spazio di visibilità a sé stante. Ossia, non solo ogni funzione costituisce uno scope separato dagli altri con il beneficio che possiamo usare gli stessi nomi di variabile in funzioni diverse senza che questi interferiscano fra di loro, ma anche ogni file racchiude il suo scope, cosicché possiamo impiegare gli stessi nomi di funzione in file diversi senza che questi influiscano gli uni sugli altri.

E altrettanto si può dire delle variabili globali, giacché in realtà una variabile globale è globale… solo all’interno del file in cui è dichiarata! Per cui esiste un metodo che ci consente di precludere la visibilità delle variabili globali a certe parti del codice.

Allo stesso tempo esiste un metodo simmetrico, che consiste nella parola magica extern, che consente, da un file, di accedere a una variabile globale situata in un altro file. Se quindi sappiamo dell’esistenza di una variabile globale in un altro file e la vogliamo usare, possiamo farlo. Se non sappiamo della sua esistenza, possiamo scrivere tranquillamente il nostro codice senza paura di dare noia a nessuno.


Tutte queste ‘misure di sicurezza’ dovrebbero consentire di farci dormire sonni tranquilli, ma non è detto che sia così perché un sistema troppo rigido renderebbe la programmazione un patimento, perciò si è cercato di rendere il meccanismo flessibile.

Quanto abbiamo detto sopra non vale per esempio se usiamo il meccanismo #include.

Cosa significa la parola #include? Si tratta di un’istruzione diretta al compilatore che fa in modo che quegli vada a prendere il file che gli indichiamo e lo copi per intero dentro il nostro. È ovvio che a quel punto, anche se noi li vediamo come file distinti, in realtà per il compilatore è come se fossero un unico file (o quasi: sto un po’ semplificando). A quel punto i nomi delle funzioni andranno in conflitto gli uni con gli altri.

Non possiamo che ammettere che include conceda una grande flessibilità al codice – pensate per esempio alla comodità di scrivere un insieme di funzioni oggi e riciclarle fra dieci anni in un programma completamente diverso con la semplice scritta #include<miofile>. Ma, come tutte le medaglie, presenta il suo rovescio, che consiste nel fatto che si possono creare conflitti inattesi fra i nomi usati.


Per fortuna sappiamo già come prevenire questi conflitti, ossia usando i namespace. I manespace non sono una cosa che riguarda le classi, ma possono essere usati anche per racchiudere le funzioni. La mia funzione nomeACaso() racchiusa nel namespace ‘nmsp1’ potrà essere invocata con la scritta nmsp1:::nomeACaso(), mentre la funzione nomeACAso() contenuta nel namespace ‘nmsp2’ sarà raggiungibile con nmsp2::nomeACaso().

Non si deve però esagerare con l’uso dei namespace. Teniamo di conto che l’intera libreria standard è racchiusa in un unico namespace, ‘std’. Nella stragrande maggioranza dei casi, se possiamo rimediare al problema con il semplice cambio di qualche lettera nel nome della funzione, facciamolo e non pensiamoci più. Certi problemi si pongono solo quando si lavora in gruppi molto vasti e su programmi di notevoli dimensioni.


Ma non esiste un modo migliore per gestire questo ‘attrito’ include/namespace?

In effetti esiste, e sono le class (nonché i template, che sono un’evoluzione delle class e rappresentano lo strumento più potente, raffinato e flessibile del C++; ma ne parleremo… un bel dì).

Se guardiamo il codice di una class, ci troviamo dentro variabili (proprietà) e funzioni (metodi).

Le proprietà sono visibili da tutti i metodi della classe, perciò assomigliano, nel loro piccolo, alle variabili globali. Diciamo che sono globali… all’interno della class.

Adesso facciamo un piccolo esercizio mentale: proviamo a visualizzare un programma composto in maniera ‘tradizionale’, ossia solo con variabili globali e funzioni, e, come se fosse scritto accanto, il codice di una class con le sue proprietà e metodi. Non vi sembra che si somiglino in modo impressionante?

In effetti pare proprio che una class riproduca, in piccolo, un programma intero; è come se fosse un programma all’interno di un programma. Non sembra che sia come un programmino specializzato contenuto in un programma più grande?


Guardandolo da questo punto di vista, ci vengono in mente due cose: la prima è che si potrebbe scrivere un intero programma in C++ usando solo le class. L’unica funzione che sopravvivrebbe all’esterno di ogni class sarebbe main(), che deve esiste perché rappresenta il punto di inizio dell’esecuzione.

Scrivere un programma con questo metodo porterebbe solo vantaggi, perché i conflitti fra i nomi sarebbero pressoché eliminati, l’esigenza di references e pointers ridotta al minimo, la possibilità di migliorare il codice riscrivendo solo una parte senza causare danni a catena su tutto il resto portata al massimo. Si può dire che non esistano controindicazioni, o almeno io non ne vedo.

E in effetti un sacco di programmi sono scritti così. Quando si parla di programmazione a oggetti s’intende in buona sostanza questo: concepire il codice come una serie di oggetti che interagiscono fra di loro. Se si riesce a concepire tutto il codice in questo modo, si ottengono grandi vantaggi da linguaggi come il C++.


La seconda considerazione è che una piccola differenza la troviamo, fra un programma ‘tradizionale’ e una class: i prototipi di funzione.

In un programma tradizionale abbiamo bisogno di specificare al compilatore quali funzioni troverà, anticipandone il nome all’inizio del codice. In questa maniera possiamo aiutare il compilatore ad accorgersi di un sacco di errori. Invece nelle class che abbiamo scritto noi questa anticipazione sembra sia scomparsa: quando abbiamo scritto l’intestazione della funzione, immediatamente sotto c’era il suo ‘corpo’, ossia l’insieme delle istruzioni che la compongono.

Il codice di Stroustrup che abbiamo visto sopra ci suggerisce però un’alternativa che ci consentirebbe di ripristinare l’equivalente dei prototipi di funzione all’interno delle class.


Per capire l’utilità di questo stile di scrittura possiamo fare un salto sul sito “cplusplus.com”

(http://www.cplusplus.com/) e seguire il link Reference.


Qt_IV_19bis.png


Nella pagina che si apre, che presenta un lungo elenco dei file che compongono la libreria standard del C++, scegliamo, nel gruppo Containers, il link <vector>; poi ancora, sotto la scritta Classes, di nuovo vector. Scorriamo la pagina finché non troviamo la scritta Member types e lì sotto selezioniamo la scheda C++11 (ormai dovremmo sapere a cosa si riferisce!).


Qt_IV_20.png


Questo sito è una delle migliori risorsi esistenti su Internet per chi vuole imparare il C++. Se scorriamo questa pagina, quella che troviamo è una sintetica ma precisa descrizione di cos’è un vector nel C++; in particolare, sotto il titolo Member functions compare un elenco dei suoi metodi pubblici.

È meglio precisare subito che un vector non è una class, bensì un template, cioè una specie di class resa ancora più flessibile e produttiva; abbiamo visto infatti che un vector può contenere qualsiasi tipo di dato, dai comuni interi (vector<int>) al testo (vector<sting>) a oggetti creati dall’utente (vector<Robot>). Questa è una caratteristica dei template, ma per tante altre cose sono identici alle class, per cui ora come ora consideriamolo una class senza complicarci troppo la vita. Esattamente come le class, i template hanno proprietà e metodi, che possono essere sia privati che pubblici.


Guardate un po’ com’è lungo l’elenco dei metodi pubblici: (constructor), (destructor), operator=, begin(), end(), rbegin(), rend(), cbegin(), cend(), crbegin(), crend(), size(), max_size(), resize(), capacity(), empty(), reserve(), shrink_to_fit(), operator[], at(), front(), back(), data(), assign(), push_back(), pop_back(), insert(), erase(), swap(), clear(), emplace(), emplace_back(), get_allocator.

Lasciamo stare che alcuni nomi, come operator[], ci possano risultare misteriosi; cerchiamo invece di raffigurarci come sarebbe cercare il capire il funzionamento di una classe dovendo leggere tutte le centinaia di righe di codice di cui è composto. Il fatto è che, per usare un vector, tutte queste informazioni non sono necessarie: a noi basta sapere cosa fa, senza stare a porci la domanda di come lo fa.


Mettiamo il caso che il programmatore abbia, con pregevole cortesia, scritto delle righe di presentazione all’inizio di ogni metodo; in tal caso a noi basterebbe leggere quelle, ignorando tutto il resto.

Come potete immaginare, non siamo i primi a cui è venuta in mente questa considerazione: il C++ offre il supporto utile a comporre le class in una maniera molto ordinata e pulita, in modo tale che, dopo averle lasciate in un cassetto per anni e anni, siamo in grado di riprenderle in mano ricostruendo in poco tempo a cosa servivano e come si usavano.

La logica del metodo è questa: il corpo dei metodi rappresenta il come, non il cosa, quindi non ci interessa leggerlo. Invece l’elenco delle proprietà e la descrizione dei metodi è ciò che ci consente di capire cosa fa una class, perciò vogliamo trovarlo ben ordinato senza tante righe inutili in mezzo.

Meglio ancora: la descrizione delle proprietà e dei metodi di una class dovrebbe trovarsi da sola su un file a parte, mentre i corpi dei metodi –in pratica i blocchi di codice fra le parentesi graffe– starebbero bene su un altro file, dove non danno noia.


Ciò può essere ottenuto con la tecnica di prototipi di funzione: in pratica in un file elenchiamo le proprietà e i prototipi dei metodi, e in un altro i corpi dei metodi.


Vediamo questa idea in pratica con la class Robot.

Il codice che a noi interessa è questo:

class Robot{
    string nome;
    int carica;
    int x;
    int y;

 public:
    Robot(string pa_nome);
    void setNome(string battezza);
    string getNome()
    void vaiSu();
    void vaiGiu();
    void vaiSx();
    void vaiDx();
    void datiASchermo();
    ~Robot();
 };

Invece quello che non ci interessa leggere è questo:

Robot(string pa_nome)
 {
    nome = pa_nome;
    x = 0;
    y = 0;
    carica = 100;
 }

 void setNome(string battezza)
 {
    nome = battezza;
 }

 string getNome()
 {
    return nome;
 }

 void vaiSu()
 {
    y += 1;
    carica -= 5;
 }

 void vaiGiu()
 {
    y -= 1;
    carica -= 5;
 }

 void vaiSx()
 {
    x -= 1;
    carica -= 5;
 }

 void vaiDx()
 {
    x += 1;
    carica -= 5;
 }

 void datiASchermo()
 {
    cout << "Robottino '" << nome << "':" << endl;
    cout << "   posizione: x = " << x << ", y = " << y << endl;
    cout << "   carica residua: " << carica << "%" << endl;
    cout << endl << endl;
 }

 ~Robot()
 {
    cout << endl << endl;
    cout << "Torna presto a trovarci!" << endl << endl;
 }

In base a ciò che abbiamo detto, quello che vorremmo è prendere quest’ultimo pezzo di codice e spostarlo su un altro file. Arrivato il momento della compilazione, diremo a Qt che quel file fa parte del nostro progetto e lui provvederà a ‘riattaccarlo’ insieme agli altri nel nostro programma.

Però ci viene subito un dubbio: come può fare il compilatore a sapere che le funzioni che trova qui sono i metodi della class “Robot”? Visto che i nomi dichiarati in una class sono invisibili all’esterno, il compilatore non ha modo di decidere se questi sono i corpi dei metodi dichiarati da noi oppure ‘normali’ funzioni scritte da qualcun altro che nulla sa della nostra class. Certo sarebbe una bella coincidenza, questo è vero, ma ciò nonostante non possiamo lasciare al compilatore la responsabilità di decidere.


Chi ha studiato con cura il codice che abbiamo ripreso da Stroustrup avrà già trovato la soluzione, che consiste nell’utilizzo del simbolo ‘::’, che, non a caso, è detto operatore di scope resolution.

Quel che dobbiamo fare è preporre al nome della funzione il nome della class di cui è metodo, ponendo fra i due il simbolo ‘::’. In pratica: Robot::datiASchermo().

In questo modo il compilatore sa che questi sono i corpi di metodi i cui prototipi sono dichiarati in un altro file, dove c’è la parte ‘utile da leggere’ della class.

I nomi delle nostre funzioni diventeranno quindi:

Robot::Robot(string pa_nome)
 {
    …
 }

 void Robot::setNome(string battezza)
 {
    …
 }

 string Robot::getNome()
 {
    …
 }

 void Robot::vaiSu()
 {
    …
 }

 void Robot::vaiGiu()
 {
    …
 }

 void Robot::vaiSx()
 {
    …
 }

 void Robot::vaiDx()
 {
    …
 }

 void Robot::datiASchermo()
 {
    …
 }

 ~Robot()
 {
    …
 }

Come dicevo, in questo capitolo non sono spiegati concetti nuovi, però non bisogna sottovalutare l’importanza di questo passaggio: le class nel C++ si scrivono con questa tecnica e qualsiasi IDE scegliessimo di usare si aspetterebbe che le volessimo così.

Il C++ è, in generale, considerato un linguaggio un po’ complicato, perciò non dovremmo mai pensare che l’ordine e la leggibilità del codice siano aspetti marginali.


Scrivere un class ha quindi una difficoltà iniziale, che è decidere a cosa serve, ossia a che fine la progettiamo. Questa è la parte più astratta, ma anche più fascinosa della programmazione a oggetti.

Dopo si incorre in una serie di difficoltà pratiche che consistono nel trovare le soluzioni alle varie problematiche che si presentano via via che si scrive il codice.

Oltre a tutto ciò, che anche una necessità di metodo e ordine che, almeno per me, rappresenta la parte più noiosa.

A voler essere completi, aggiungerei che tutto ciò che scriviamo dovrebbe essere documentato, ossia accompagnato da numerose righe di commento che ci rendano più semplice ricordarci cosa avevamo in mente quando l’abbiamo scritto.


Non credo sia un caso se tutti gli IDE si offrono di darci una mano sotto l’aspetto dell’ordine. Può darsi che non sia il solo a trovarlo un passaggio un po’ ripetitivo. Anche Qt avrebbe voluto semplificarci un po’ la vita, se non avessimo respinto il suo aiuto. È arrivato però il momento di scoprire come fare in modo che, al momento della progettazione di una class, Creator si occupi al posto nostro di:

  • aggiungere due file al nostro progetto;
  • predisporre uno dei due perché contenga la parte leggibile di una class;
  • predisporre l’altro perché ospiti i corpi dei metodi.

Per scoprire queste funzioni, chiudiamo il progetto aperto e tutti i relativi files e creiamo un Robot2, che finirà per essere identico a Robot se non per il fatto che il codice sarà, una buona volta, in ordine.


Inserisco le immagini delle operazioni, ma, se la vostra versione di Qt è in lingua italiana, non saranno identiche.

Le prime dovrebbero essere assai familiari: nuovo progetto


Qt_IV_21bis.png


Scegliere un progetto senza le librerie Qt (Non-Qt Project) e, nella colonna centrale, un progetto C++ vuoto (Plain C++ Project).


Qt_IV_22.png


Selezionare la cartella e scegliere un nome coerente – io scrivo Robot2:


Qt_IV_23.png


Nelle schermate successive lasciare tutto com’è.

Quando ci si trova davanti il nostro nuovo progetto ‘pulito’, eccetto per il codice ‘Hello Word’, chiedere a Creator di aggiungere una nuova class al progetto. Per far ciò, selezionare File → Nuovo file o progetto (New File or Project):


Qt_IV_24bis.png


Nella schermata che segue selezionare C++ Files and Classes: C++ → C++ Class. Infine confermare.


Qt_IV_25bis.png


Se però si presta attenzione a cosa compare scritto nella colonna di destra, si avranno informazioni su ciò che sta per succedere. Infatti Creator ci comunica che, in base alla nostra scelta, verranno creati due file, uno header e un source. È ciò che desideriamo noi perché un header è un tipo di file di testo, con estensione di solito .h, che è idoneo a contenere la parte utile da leggere di una class; un source un file, nel C++ spesso con estensione .cpp, dove ci si può aspettare di trovare i corpi dei metodi.

Andiamo avanti.


Nella schermata successiva, specificate, come nome della class, Robot. Potete scrivere Robot indipendentemente da come avete chiamato il progetto, giacché possiamo creare una class Robot in qualsiasi programma non ce ne sia già una – o meglio, non ce ne sia già una che non sia nascosta in un namespace.

Lasciate tutto il resto com’è.

Creator vi sta già segnalando come intende chiamare i due file: lo header “robot.h” e il source “robot.cpp”. Potremmo scegliere qui altri nomi, ma chi ce lo fa fare?


Qt_IV_26.png


Andiamo alla schermata successiva.

Questo è soltanto un riepilogo. Potete studiarlo per confermarvi di aver capito cosa sta per succedere, poi dare il vostro assenso.


Qt_IV_27.png


Ecco che siamo tornati al nostro progetto.

Però, se guardate bene, non è più come prima: nella colonna centrale compare una nuova scritta: “Headers”. Se fate click sulla freccetta nera sulla sinistra, si apre e lascia apparire il nostro primo header file: “robot.h”.

Più in basso, nella stessa ‘categoria’ di “main.cpp” (che ora scopriamo essere un source file) troviamo il corrispondente “robot.cpp”.


Qt_IV_28bis.png


A parte ricordarsi i menu da scegliere, mi sembra che da un punto di vista concettuale ancora non abbiamo detto niente di difficile.

Adesso, visto che il numero dei file del nostro progetto per la prima volta è superiore a uno, conviene capire come si fa a spostarsi dall’uno all’altro.


L'impostazione di Creator, che io trovo molto comoda, ma non è detto piaccia a tutti, è di riempire sempre la colonna di destra con il file che si è selezionato. In altre parole, se in questo momento abbiamo scelto “robot.cpp”, il codice che vediamo a destra è il contenuto del file “robot.cpp”.

Per spostarsi in un altro file ci sono due metodi: il più naturale consiste nel fare doppio click sul nome del file nella colonna centrale che si vuole visualizzare.

Proviamo ad esempio a entrare in “robot.h” con questo sistema:


Qt_IV_29bis.png


L’altro sistema, che pare meno spontaneo, finisce per essere il preferito dopo un po’ di tempo che si usa Creator. Se guardate sopra la colonna di destra, trovate una barra grigio scuro con una serie di simboli. In uno di quelli è riportato il nome del file visualizzato, “robot.h”.

Se facciamo click sulla doppia freccia verticale che c’è alla destra di questo nome, si apre un comodo menù tramite il quale possiamo passare rapidamente da un file all’altro:


Qt_IV_30bis.png


Adesso diamo una breve occhiata al codice che Qt ha pensato bene di inserire senza che noi glielo chiedessimo.

Possiamo partire dal presupposto che, se l’ha fatto, ci deve essere un motivo. Il motivo, però, ora sarebbe un gran noia tecnica da spiegare, anche se non c’è niente di difficile. Visto che questa parte del tutorial sta diventando lunghissima, diciamo che ce lo lasciamo in caldo per un’altra puntata. A noi può bastare sapere che quelle righe che iniziano con ‘#’ sono istruzioni per il compilatore ed è meglio se le sciamo dove stanno. Un giorno, però, impareremo a metterci le mani.


La parte centrale del file è invece un comodo aiuto che Creator ci offre per farci scrivere meno codice: visto che l’abbiamo informato che volevamo scrivere una class di nome “Robot”, ha già iniziato a farlo lui per noi.

Ecco lì il blocco iniziale, con il suo punto e virgola finale, la scritta public perché tanto c’è pressoché sempre, nonché la dichiarazione del costruttore. Avevamo detto o no che conveniva specificarlo sempre? Beh, ci ha già pensato Creator.


Adesso facciamo una scappata in “robot.cpp” e troviamo un passaggio molto importante:

#include "robot.h"

Vi sembra strano?

Dovrebbe essere ovvio: “robot.cpp” e “robot.h” sono due componenti dello stesso oggetto, la class “Robot”. Non possono vivere due vite separate. Il Compilatore deve sapere dove andare a trovare i prototipi delle funzioni che incontra qui, e quelli sono contenuti in “robot.h”. O, per meglio dire: ancora non ci sono contenuti, ma ce li stiamo per inserire noi!

Forza, al lavoro, pigroni!

Il prossimo esercizio è questo: copiate il contenuto del progetto “robottino” in “Robot2”, stando ben attenti a inserire le cose giuste al posto giusto. Alla fine dei giochi, il programma deve compilare e eseguire correttamente.

Attenzione! Vi dovrebbero rimanere solo due funzioni esterne alla classe, e una delle due è main().

Altra cosa: ricordatevi che in “robot.cpp” è dovuta comparire un’istruzione #include… Nel file “main.cpp” cosa dovrà essere aggiunto?


Se pensate che vi dia la soluzione qui, vi sbagliate :-O

Si tratta solo di copiare e ragionare un po’, suvvia! E poi non abbiamo tempo, perché ci aspetta qualcosa di new.


Qualcosa non poi così new

PREMESSA. Per i prossimi esempi sono tornato a sfruttare il progetto “Prove”, che abbiamo detto ci sarebbe servito per scrivere codice misto.

Come al solito chi non vuole perdere il vecchio codice può o salvarlo o creare un nuovo progetto, sempre di tipo non-Qt.


I nei dei puntatori

Vi ricordate cosa commentavamo all’inizio di questo quarto tutorial?

Si diceva che un linguaggio che ammettesse solo variabili globali e locali senza prevedere altre soluzioni sarebbe un po’ rigido. Poi abbiamo scoperto che le class ci consentivano soluzione intermedie.


Adesso pensiamo un po’ ai puntatori e chiediamoci se non ci siano degli aspetti nei quali li miglioreremmo.

Abbiamo visto per esempio che un puntatore può essere dichiarato senza avere nulla da puntare, ma in quel caso è di ben scarsa utilità.

Oltre a ciò, i puntatori che abbiamo visto fin qui paiono davvero un po’ scomodi: infatti bisogna prima creare un ‘qualcosa’ in memoria, tipo una variabile intera o una variabile oggetto, e solo dopo che essa esiste allora si può passare il suo indirizzo al puntatore. Pare quindi che, per poter usare un puntatore, si debba comunque già avere una variabile. Il puntatore, a queste condizioni, appare una sorta di duplicato, una specie di passaggio intermedio obbligato per poter far modificare una variabile a un’altra funzione.


Continuando a ragionarci su, ci vengono altri dubbi sull’utilità dei pointers. Vi siete chiesti che cosa succede al valore di ritorno di una funzione se quel valore è un puntatore? In questo caso la restituzione di valori al chiamante procederà senza errori? Mi spiego con un esempio.

Mettiamo di avere una funzione che riceve restituisce un pointer a int Un esempio potrebbe essere:

int* restUnInt()
 {
    int numInt = 47; // Un numero a caso
    int* daRestituire = &numInt;

    return daRestituire;
 }

La funzione chiamante, che può essere anche main(), deve soltanto creare un puntatore che riceva il valore di ritorno di restUnInt():

int main()
 {
    int* valRitorno;

    valRitorno = restUnInt();
    cout << "valRitorno = " << *valRitorno << endl << endl;
 }

Secondo voi questo programma funzionerà? Ci trovate qualche difetto?

Quale sarà l’output?

Chi scommette su “valRitorno = 47”?


Mentre ci pensate, riporto il codice completo a uso e consumo dei (soliti) pigroni:

#include <iostream>

 using std::cout;
 using std::endl;

 int* restUnInt();

 int main()
 {
    int* valRitorno;

    valRitorno = restUnInt();
    cout << "valRitorno = " << *valRitorno << endl << endl;
 }

 int* restUnInt()
 {
    int numInt = 47; // Un numero a caso
    int* daRestituire = &numInt;

    return daRestituire;
 }

Vediamo come è andata la scommessa. Il mio risultato è il seguente:


Qt_IV_31.png


Parrebbe quindi che chi ha scommesso sul 47 abbia vinto. Ma la vittoria sarebbe valida lo stesso se fosse dovuta a un puro caso?

Vi suona strano che l’output di un programma possa dipendere dal caso?

Ebbene, forse vi sorprenderà sapere che il fatto che a me, sul mio computer, il programma abbia restituito 47 non implica che a qualcun altro non abbia dato un risultato completamente diverso. Le variabili in gioco sono tante e la prima è di quanta memoria dispone il vostro computer.


Ciò di cui stiamo parlando in realtà è la naturale conseguenza di cose già viste, ma cercheremo lo stesso di entrare nell’argomento passo passo.

Alle persone cui il programma ha risposto 47 sarà rimasta l’impressione che non ci sia niente di strano. La variabile ‘numInt’ è stata inizializzata con il valore 47; il puntatore ‘daRestituire’ ha ricevuto l’indirizzo di ‘numInt’, perciò ha iniziato a tenere di mira un’area di memoria dove era contenuto il valore 47.

Infine copia di questo indirizzo è stata passata a ‘valRitorno’, perciò anche questo puntatore ha iniziato a tenere di mira un’area di memoria dov’era contenuto il valore 47…


Mmmh! Ne siete proprio sicuri?

Lasciate che vi faccia una domanda. Se così fosse, allora il puntatore continuerebbe a puntare a un’area di memoria dov’è contenuto il valore 47 anche in seguito. Ossia, se io dereferenziassi il puntatore dopo qualche riga di codice, dovrei avere sempre la stessa risposta. Sempre 47. Giusto?


Ebbene proviamo:

#include <iostream>

 using std::cout;
 using std::endl;

 int* restUnInt();
 void qualcheOperazione();

 int main()
 {
    int* valRitorno;

    valRitorno = restUnInt();
    cout << "valRitorno = " << *valRitorno << endl;

    qualcheOperazione();

    cout << "valRitorno = " << *valRitorno << endl << endl;
 }

 int* restUnInt()
 {
    int numInt = 47; // Un numero a caso
    int* daRestituire = &numInt;

    return daRestituire;
 }

 void qualcheOperazione()
 {
    int i1 = 7; // un numero a caso
    int i2 = 19;   // un numero a caso

    cout << "7 x 19 = " << i1 * i2 << endl;
 }

Ho apportato delle modifiche minime al codice e nessuna di queste tocca il puntatore ‘valRitorno’.

Anzi, per essere sicuro di non interferire, ho spostato le operazioni in un’altra funzione, qualcheOperazione(), che non accetta parametri e non restituisce nulla. Potete verificare con i vostri occhi: ‘valRitorno’ è rimasto inalterato. Punta quindi sempre alla stessa cella di memoria.

E il risultato che ottengo è questo:


Qt_IV_32.png


La cosa più strana è che a qualcuno ‘valRitorno’ potrebbe continuare a restituire 47, mentre ad altri potrebbe non farlo già la prima volta.


Ma c’è davvero qualcosa di strano?

Ripensiamo a ciò che abbiamo detto a proposito delle funzioni. Quando una funzione termina, il suo spazio di memoria viene cancellato e riutilizzato per altre funzioni o altri programmi. La variabile ‘numInt’, il cui indirizzo stiamo tenendo di mira, per l’appunto era una variabile locale a una funzione, restUnInt(). Quando restUnInt() è terminata, tutta la memoria che occupava è stata dichiarata libera e disponibile. Da quel momento, non c’era più nessuna garanzia di quale fosse diventato il valore dell’area di memoria con etichetta ‘numInt’.


Ovviamente non è possibile prevedere quando il computer riuserà esattamente quella memoria. Sul mio pc è successo che, in un primo momento, non ne avesse bisogno, perciò l’ha lasciata stare. Avendola lasciata stare, quando ho dereferenziato ‘valRitorno’ la prima volta il valore che era presente nella cella di memoria che era stata etichettata ‘numInt’ era ancora 47.

Nel momento in cui però l’esecuzione del programma ha chiesto al computer un po’ di memoria per eseguire una nuova funzione, qualcheOperazione(), questi ha messo a disposizione quella che prima era la memoria di restUnInt(). A quel punto il valore di quella cella di memoria è stato modificato in una maniera imprevedibile.

Fatto sta che ‘valRitorno’ ha continuato a puntare lì, perché era quello che gli avevamo ordinato di fare.


Il mio economico portatile con 4Gb di RAM ha dovuto riciclare la stessa memoria abbastanza in fretta, ma se avete un buon pc con 16Gb può darsi che il sistema operativo non la tocchi per alcuni secondi. Detto nella maniera più semplice possibile: è imprevedibile. Il risultato cambierà a ogni esecuzione in base a quali programmi sono attivi e a mille altri fattori su cui non possiamo fare niente.


A questo punto sembrerebbe che avessimo trovato un nuovo difetto ai nostri amici puntatori: a una prima vista parrebbero poco idonei per costituire valori di ritorno per le funzioni.

Questa è però più un’apparenza che una sostanza perché sono usatissimi anche a tale scopo, solo con alcuni accorgimenti.

Tanto per citarne uno, se passate un puntatore a una funzione, vuol dire che state passando a quella funzione l’indirizzo di un’area di memoria che si trova al di fuori della funzione stessa. La conclusione è che quell’aria di memoria non sarà distrutta al termine della funzione. È quindi possibile restituire con sicurezza un nuovo puntatore a quell’area, se la cosa può essere utile.


Una memoria indistruttibile

Scava scava, siamo riusciti a trovare degli aspetti in cui anche i puntatori sembrano deluderci un po’. In realtà, più che veri e propri difetti bisognerebbe parlare di una filosofia sottostante al linguaggio C++.


Una caratteristica del C++ che alcuni considerano senza mezzi termini un difetto è il fatto che non offre molte tutele al programmatore. In buona sostanza, al compilatore non è richiesto di metterci in guardia se facciamo una cosa poco sicura come chiedere di accedere a un’area di memoria che non sappiamo dove si trovi né cosa ci sia dentro. L’avevamo già visto nel tutorial precedente, solo che in quel caso il compilatore almeno un avviso era riuscito a darcelo; adesso non otteniamo nemmeno quello.

Per chi è abituato a altri linguaggi questo comportamento è censurabile perché, secondo alcuni, le cose poco sensate andrebbero proibite; sul lato opposto ci sono programmatori appassionati di C++ che si fanno quasi un vanto del fatto che per padroneggiare questo linguaggio servano tempo e fatica.


Come al solito mi permetto un commento laico: il C++ è un linguaggio come tanti; ad alcuni piacerà, ad altri no. Ci sono dei linguaggi che cercano di aiutare il programmatore a non sbagliare; la politica del C++ è invece: tutto ciò che non è esplicitamente proibito, è concesso. Che poi funzioni, è un altro paio di maniche :-)

Secondo me la discussione si risolve ammettendo che nel C++ si deve procedere passo passo, senza fretta, cercando di sviscerare i pro e i contro delle soluzioni che offre.


In questa situazione, per esempio, alle caratteristiche dei puntatori che non ci piacciono offre una soluzione, che è la parola magica new. Anche di questa bisognerà capire vantaggi e svantaggi.


Cominciamo da una domanda dall’apparenza un po’ superficiale: ma perché insisto a chiamarla parola magica? Non potrei chiamarla istruzione, come ho fatto per for, while, if…?

Beh, in fondo non verrebbe nessuno a bastonarmi –o almeno spero–, ma rimane il fatto che new di per sé nel C++ è definita come un operator, un operatore. Rientra cioè nel gruppo dei simboli come ‘==’, ‘[]’, ‘{}’, ‘::’, ‘*’, eccetera. La differenza è che è composta di lettere e non di asterischi o parentesi o due punti, ma la cosa non deve portarci fuori strada.


Dovete sapere che nei corsi seri sul C++ c’è sempre un punto in cui vengono presentate lunghe tabelle con l’elenco dei simboli e di quello che si chiama ‘ordine di precedenza’ o di ‘valutazione’. Non voglio negare la loro importanza, ma ribadisco che qui vogliamo fare una carrellata generale, cercando di rompere il ghiaccio con il linguaggio, e non di sviscerarlo in maniera scientifica. Perciò rimanderemo anche questo noiosiss… ehm, importantissimo argomento.


Cosa fa dunque new?

Lo si può dire in due parole: new è capace di costruire un’istanza dell’oggetto che desideriamo direttamente in memoria e di restituire un puntatore a quell’oggetto.


Se non è chiaro, due righe di codice renderanno tutto comprensibile.

Per creare un oggetto fin’ora avevamo un unica possibilità: definire un variabile che fungesse da ‘etichetta’ di quell’oggetto:

Robot istanzaDiRobot;

A questo punto ci trovavamo con un oggetto, come dire, ‘reale’, che occupava un’area di memoria.

Se invece dichiaravamo un puntatore allo stesso tipo di oggetto:

Robot* puntAOggettoRobot;

ci trovavamo con un puntatore, ma non con un oggetto, nel senso che nessuna istanza di class andava a occupare alcuno spazio di memoria.


Se infine volevamo un puntatore che davvero puntasse a un oggetto esistente, non avevamo altra scelta che creare un’istanza di class e poi passare il suo indirizzo al puntatore:

puntAOggettoRobot = &istanzaDiRobot;

Riassumendo, le istruzioni erano come minimo due:

Robot istanzaDiRobot;   // creo l’oggetto in memoria
 Robot* puntAOggettoRobot = &istanzaDiRobot; // ottengo un pointer all’oggetto

Un po’ una perdita di tempo, forse.

new consente di riassumere queste due istruzioni in una sola:

Robot* puntAOggettoRobot = new Robot;

Il risultato di questa istruzione è che viene creato un’istanza della class “Robot”, ma non le viene data alcuna etichetta; però viene inserito il suo indirizzo di memoria nel puntatore ‘puntAOggettoRobot’.

Da questo momento sarà possibile accedere all’oggetto creato tramite il puntatore, anche se non ha un ‘nome’.


Il vantaggio incredibile di creare un oggetto con l’istruzione

Robot* puntAOggettoRobot = new Robot;

anziché con

Robot istanzaDiRobot;

è che, trovandoci in mano un puntatore, possiamo passarlo senza problemi come parametro alle varie funzioni, rendendolo di fatto modificabile da ogni punto del programma, ma senza bisogno di dichiararlo globale.

In altre parole, abbiamo un oggetto che non è globale, ma con semplici accorgimenti può comportarsi come se lo fosse.

Pensate un po’ alla differenza: una variabile globale può essere modificata per errore da qualcuno che non sa della sua esistenza; al contrario, un oggetto non globale può essere modificato solo da chi sa della sua esistenza. Inoltre, a meno che un programmatore non sia così sempliciotto da non tener di conto dei nomi degli argomenti della funzione su cui sta lavorando, è pressoché impossibile che il nome del puntatore sia mascherato per errore.


Quindi new sembrerebbe un buon rimedio al primo ‘neo’ dei puntatori: il fatto che, per essere davvero utili, ci obbligano a trovar loro un oggetto già esistente in memoria da tener di mira.

Ma sarà in grado di aiutarci anche nella seconda, ossia che i puntatori non sembrano essere la scelta migliore per far restituire valori alle funzioni?


Per rispondere al quesito è necessario dare un’occhiata a come fa a fare ciò che fa; solo dopo potremo valutare pro e contro.


Cercate di prevedere quale potrebbe essere lo output del seguente codice prima di andare a guardarlo:

#include <iostream>

 using std::cout;
 using std::endl;

 class Qualsiasi{}; // la class è vuota, ma si puo' usare lo stesso

 void indagineSuNew();

 Qualsiasi globalQual;   // istanza della classe Qualsiasi
                         // con visibilità globale

 int main()
 {
    Qualsiasi mqual; // nome a caso
    Qualsiasi* p_mqual = &mqual;
    Qualsiasi* p2_mqual = new Qualsiasi;

    cout << "Indirizzo di globalQual = "
         << (unsigned long) &globalQual << endl;

    cout << "Indirizzo di mqual = " << (unsigned long) p_mqual << endl;
    cout << "Indirizzo di p_mqual = " << (unsigned long) &p_mqual << endl;
    cout << "Indirizzo di p2_mqual = "
         << (unsigned long) p2_mqual << endl;
    indagineSuNew();
 }

 void indagineSuNew()
 {
    Qualsiasi fqual; // nome a caso
    Qualsiasi* p_fqual = &fqual;
    Qualsiasi* p2_fqual = new Qualsiasi;

    cout << "Indirizzo di fqual = " << (unsigned long) p_fqual << endl;
    cout << "Indirizzo di p_fqual = " << (unsigned long) &p_fqual << endl;
    cout << "Indirizzo di p2_fqual = "
         << (unsigned long) p2_fqual << endl << endl;
 }

Questo è il risultato che viene a me – i vostri numeri potrebbero essere completamente diversi.


Qt_IV_33.png


Ok, sono un sacco di numeri, però se li osserviamo bene ci forniscono anche un sacco di informazioni.

Quello che abbiamo chiesto, e ottenuto, è l’indirizzo di memoria di tutte le variabili dichiarate, ivi compresi i puntatori e le variabili globali.

Proviamo a spostare le righe ottenute in base all’indirizzo di memoria. Finiamo per trovarci in questa situazione:


variabile globale 134520885
oggetto ‘new’ in main() 156848136
oggetto ‘new’ in indagineSuNew() 156848152
variabile in indagineSuNew() 3219612663
puntatore in indagineSuNew() 3219612664
variabile in main() 3219612711
puntatore in main() 3219612712


Cosa ci suggerisce questa tabella?

A me sembra voglia dire che, anche se sono definiti in funzioni diverse, gli oggetti creati tramite new si vanno a posizionare in un’area di memoria che è distante da quella usata dalle funzioni, e più vicina a quella usata dagli oggetti globali. In altre parole, pare che gli oggetti creati con new godano di speciali privilegi, tra cui quello di dimorare in un’area di memoria diversa da quella in cui operano le ‘normali’ variabili definite all’interno delle funzioni.


Bene, questa apparenza è una realtà di fatto e ha una conseguenza molto importante, ossia che gli oggetti creati con new NON muoiono insieme alla funzione. Giacché risiedono in uno spazio di memoria separato, quando una funzione viene conclusa il compilatore non si occupa di loro: esso si limita a rimettere lo spazio di memoria della funzione nel grande mucchio di quella utilizzabile, ma non va a ‘inseguire’ gli oggetti creati con new per recuperare anche i loro indirizzi di memoria.

Dicendo la stessa cosa al positivo, new crea degli oggetti che sopravvivono alla distruzione della funzione all’interno della quale sono stati dichiarati, pertanto possono essere puntati con tranquillità anche dopo.

Di conseguenza, recuperando e modificando il codice scritto in precedenza, il seguente programmino non solo non dà errori in fase di compilazione, ma è anche formalmente corretto (anche se pressoché inutile, ma questo non ci interessa):

#include <iostream>

 using std::cout;
 using std::endl;

 int* restUnInt();
 void qualcheOperazione();

 int main()
 {
    int* valRitorno;

    valRitorno = restUnInt();
    cout << "valRitorno = " << *valRitorno << endl;

    qualcheOperazione();

    cout << "valRitorno = " << *valRitorno << endl << endl;
 }

 int* restUnInt()
 {
    int* daRestituire = new int; // new non e' solo per gli oggetti!
    *daRestituire = 47;

    return daRestituire;
 }

 void qualcheOperazione()
 {
    int i1 = 7; // un numero a caso
    int i2 = 19;   // un numero a caso

    cout << "7 x 19 = " << i1 * i2 << endl;
 }

L’oggetto puntato da ‘daRestituire’ è ancora perfettamente in forma dopo la distruzione di restUnInt(), giacché ne ha sentito solo una lontana eco, perciò può continuare a essere puntato da ‘valRitorno’ senza controindicazioni.


Questo aggiunge un altro tassello alla riflessione che facevamo prima: se creiamo un oggetto con new, otteniamo qualcosa che possiamo riuscire a usare come se fosse globale senza esserlo, anche perché il compilatore non si occupa mai di distruggerlo!

Quindi, a differenza delle variabili globali, può essere creato in qualsiasi punto del programma e da lì vivere fino alla fine.


Insomma, questo operatore new pare proprio una gran cosa. Viene da domandarsi come abbiamo fatto a farne fin’ora a meno.

È proprio il caso di andare a vedere perché può arrivare a devastare anche il programma meglio progettato.


Perdere la memoria

Cos’è dunque che fa new? Crea un oggetto in memoria e restituisce un puntatore.

Non sembra una cosa molto pericolosa.

Bisogna prestare però attenzione al secondo dettaglio: l’oggetto non sarà mai distrutto. Le conseguenze sono abbastanza chiare: la memoria da lui occupata non tornerà più disponibile.


Vi sembra un problema molto tecnico? Beh, in effetti stiamo scrivendo programmini così brevi che la memoria che andiamo a impegnare è una minuscola frazione di quella disponibile in un computer moderno, ma non è certo questa la situazione tipica di chi sceglie il C++ come linguaggio di sviluppo.

Proviamo a immaginare una situazione in cui un oggetto viene creato con new in una funzione che viene ciclicamente invocata durante l’esecuzione del programma. Ogni volta il puntatore all’oggetto creato con new verrà distrutto all’uscita, ma l’oggetto no, pertanto a ogni chiamata ne verrà creato uno nuovo. A seconda del numero di chiamate e delle dimensioni dell’oggetto creato si potrebbe in poco tempo giungere a esaurire tutta la memoria disponibile. Nel momento in cui il programma chiede troppa memoria, il sistema operativo si difende chiudendolo in modo forzato.

Simpatico, vero?


Qualcuno forse a questo punto si starà grattando la testa pensieroso. Possibile che a una fascinosa invenzione come operator new non sia stata fornita una qualche utilità che consenta di usarla senza danni?

Beh, in effetti sono stato un po’ drammatico. Non avrei dovuto dire che l’oggetto non sarà mai più distrutto, ma piuttosto che il compilatore non prenderà mai l’iniziativa di distruggerlo.

Non è una finezza verbale: intendo dire che il compilatore di sua iniziativa non lo distruggerà, ma noi possiamo obbligarlo a farlo. Esiste in fatti l’opposto dell’operatore new, che è delete.


delete distrugge un oggetto creato con new.

La sua sintassi è molto semplice:

Robot *p_robot = new Robot;
 delete p_robot;

Niente di più.


Allora è semplicissimo. Tutto risolto, quindi? Possiamo usare new a piacere?

In teoria sì, in pratica potrebbe non essere così semplice: vista la quantità di domande che si trovano su questo argomento nei forum, direi che un sacco di gente si trova in difficoltà a usare new e delete, perciò forse conviene dar loro una seconda occhiata.


Anche se stiamo di nuovo parlando di memoria e puntatori, l’argomento non è difficile. Il difficile è magari applicarlo nella pratica. Il fatto, detto papale papale, è che è davvero molto molto semplice dimenticarsi di delete. Sembra una sciocchezza, ma è così. Vediamo perché.


Intanto mettiamoci d’accordo su una regola generale: tutti gli oggetti creati con new devono essere distrutti; o, con altre parole, su tutti gli oggetti creati con new bisogna ricordarsi di usare delete.


Quella sopra è una regola ineludibile. Insisto: il C++ non offre il meglio di sé quando un singolo programmatore progetta un piccolo software; il C++ è uno strumento eccezionale per un gruppo che vuole tirar su un progetto di una certa dimensione. I vostri colleghi borbotteranno se cominciate a lasciare oggetti inutilizzati in memoria.

Però le cose non sono così semplici. Poniamo il caso di iniziare a lavorare su una parte del codice a inizio settimana. Poi capita il classico imprevisto che costringe a lasciare tutto lì per qualche giorno mentre si devono portare avanti altre cose. Alla fine si può riprendere in mano il codice di giorni o settimane prima, ma… A quel punto chi se lo ricorda più che in qualcuna di quelle decine e decine di righe avevamo lasciato un new in sospeso?


Il fatto è, cercate di immaginare la situazione, che il codice funziona benissimo. Non ci sono difetti!

Il mancato uso di delete è uno di quei vizi subdoli e nascosti che fanno dannare a ritrovarli. La gestione della memoria è, in generale, uno di quegli argomenti su cui sono state spesi fiumi di parole.

Vediamo se ci sono delle soluzioni semplici a questa istintiva e universale tendenza alla pasticcioneria.


Una delle più simpatiche che ho sentito è stata: basta scrivere subito l’istruzione delete. In altre parole, quando creiamo un oggetto con new, al rigo sotto lo distruggiamo subito con delete, salvo poi spostare quest’ultima istruzione in basso via via che aggiungiamo codice fra le due.

L’idea è carina e non ho nulla da obiettare, a parte la scomodità. Però si può, oltre ad applicare questa se piace, anche cercare di farci qualche domanda prima di usare new.


Infatti la questione è: perché mi serve new?

Si potrebbe pensare a un mero problema di comodità. Invece di scrivere:

Robot miorobot("Mario");
 Robot* p_miorobot = &miorobot;

scrivo solo:

Robot* p_miorobot = new Robot("Mario");

e risparmio un rigo.

Chi la pensa così probabilmente sta leggendo troppo di corsa questi paragrafi. In realtà non rispalmiamo niente perché prima o poi dovremo scrivere:

delete p_miorobot;

perciò, due righe nel primo caso, due righe nel secondo… abbiamo fatto pari.


L’altra possibilità, che mi sembra più frequente, è quella in cui abbiamo bisogno di un oggetto che decideremo noi quando morirà.

Ecco: questa è un’esigenza vera, e non soltanto la ricerca di una comodità. Ma quando può capitare una cosa del genere? Beh, l’esempio classico è nell’interazione con l’utente.

Immaginate di stare lavorando sul vostro programma di videoscrittura preferito. A un certo punto siete costretti a lasciare in sospeso quello che stavate facendo e a scrivere una cosa diversa. quale che sia il programma che state usando, è assai probabile che abbia un comando del tipo File → Nuovo documento. Scegliendo quel comando, vi si apre una nuova pagina bianca che va a coprire quella su cui stavate lavorando. E se lo selezionate di nuovo, si aprirà ancora un nuovo “documento”.


Quel che il comando fa è di invocare una funzione (che molto probabilmente sarà un metodo di una class) la quale crea una nuova istanza “documento”. Finito il lavoro di creazione, la funzione si conclude, ma il nuovo “documento” non deve morire con lei! Anzi! Potrebbe essere che gli sia richiesto di rimanere disponibile fino alla chiusura del programma.

A me sembrerebbe la situazione ideale per utilizzare new: posso creare un nuovo oggetto direttamente dentro una funzione, ma sapendo che rimarrà in vita finché non lo distruggerò io; non solo, ma posso renderlo visibile al resto del programma con semplicità restituendo un puntatore, ossia un modesto intero che occupa pochissima memoria. Se l’avessi creato con il metodo ‘tradizionale’ il computer si sarebbe dovuto occupare di fare una costosissima (in termini di tempo e risorse) copia per la funzione chiamante.


Se avete colto l’essenza dell’esempio precedente, non vi sorprenderà sentir dire che molto spesso new è usato all’interno delle class. Infatti questo rende possibile scrivere tutte le istruzioni delete che servono dentro il distruttore.

L’idea di fondo è che, male che vada, quando l’istanza di class viene distrutta, l’invocazione del metodo distruttore comporterà l’esecuzione di tutte le funzioni delete in esso contenute, quindi, anche se ci fossimo dimenticati di distruggere qualche oggetto al momento giusto, almeno a quel punto sarà distrutto.


Con questo possiamo lasciare anche new e delete.


Per chi non ce l’avesse fatta

Quel che segue è il codice di Robot2 nel caso qualcuno non fosse riuscito a dividerlo correttamente nei vari file.


file “robot.h”

#ifndef ROBOT_H
 #define ROBOT_H

 #include<iostream>

 using std::string;

 class Robot
 {
    string nome;
    int carica;
    int x;
    int y;

 public:
    Robot();
    Robot(string pa_nome);
    void setNome(string battezza);
    string getNome();
    void vaiSu();
    void vaiGiu();
    void vaiSx();
    void vaiDx();
    void datiASchermo();
    ~Robot();
 };

 #endif // ROBOT_H

File “robot.cpp”

#include "robot.h"

 using std::cout;
 using std::endl;

 Robot::Robot()
 {
    nome = "Senza nome";
    x = 0;
    y = 0;
    carica = 100;
 }

 Robot::Robot(string pa_nome)
 {
    nome = pa_nome;
    x = 0;
    y = 0;
    carica = 100;
 }

 void Robot::setNome(string battezza)
 {
    nome = battezza;
 }

 string Robot::getNome()
 {
    return nome;
 }

 void Robot::vaiSu()
 {
    y += 1;
    carica -= 5;
 }

 void Robot::vaiGiu()
 {
    y -= 1;
    carica -= 5;
 }

 void Robot::vaiSx()
 {
    x -= 1;
    carica -= 5;
 }

 void Robot::vaiDx()
 {
    x += 1;
    carica -= 5;
 }

 void Robot::datiASchermo()
 {
    cout << "Robottino '" << nome << "':" << endl;
    cout << "   posizione: x = " << x << ", y = " << y << endl;
    cout << "   carica residua: " << carica << "%" << endl;
    cout << endl << endl;
 }

 Robot::~Robot()
 {
    cout << endl << endl;
    cout << "Torna presto a trovarci!" << endl << endl;
 }

File “main.cpp”:

#include <iostream>
 #include <robot.h>

 using std::cin;
 using std::cout;
 using std::string;

 int muoviRobot(Robot &rbt);

 int main()
 {
    Robot rbt("Mario");
    // rbt.setNome("Mario");

    while(muoviRobot(rbt))
       rbt.datiASchermo();

    return 0;
 }

 int muoviRobot(Robot &rbt)
 {
    string testo;
    cout << "Direzione? ";
    cin >> testo;

    if("a" == testo) {
       rbt.vaiSx();
       return 1;
    } else if ("w" == testo) {
       rbt.vaiSu();
       return 1;
    } else if ("s" == testo) {
       rbt.vaiDx();
       return 1;
    } else if ("z" == testo) {
       rbt.vaiGiu();
       return 1;
    } else if ("0" == testo) {
       return 0;
    }
    return 2; // 2 == tasto non riconosciuto
 }

Ci trovate qualcosa di strano?

In effetti uno dei metodi si è duplicato: il costruttore Robot(). Adesso pare che ne esistano due versioni…

Beh, è vero fino a un certo punto. Si tratta di una caratteristica del C++ che sono stato indeciso fino a qui se trattare o no, e alla fine ho deciso per un compromesso: la accenno soltanto :-)


Si tratta di un meccanismo che si chiama overload di funzione e non è difficile.

In pratica, nel C++ una funzione non si riconosce soltanto dal suo nome, ossia dall’etichetta scritta prima della parentesi tonda, ma anche dai suoi argomenti.

Quindi se ci sono due funzioni con lo stesso nome, ma con argomenti differenti, per il compilatore si tratta di funzioni differenti e possono coesistere nello stesso spazio di visibilità.


Nel nostro caso, il metodo costruttore Robot() senza argomenti è, per il compilatore, una funzione diversa da Robot(string) che ha un argomento di tipo string.

Quindi, se all’atto della creazione dell’istanza di classe viene scritto:

Robot rbt("Mario");

come nel nostro caso, verrà invocato il metodo costruttore Robot(string).

Se invece venisse scritto:

Robot rbt;

l’oggetto verrebbe creato regolarmente tramite il metodo Robot(), il quale assegna alla proprietà ‘nome’ il testo “Senza nome”.


Anche se non fosse del tutto chiaro, non vi preoccupate: ne riparleremo usando le librerie Qt.

Che è ciò che ci apprestiamo a fare (anche se dovremo fare prima i conti con l’ereditarietà).