Cerca nel sito:
ricerca
avanzata

Frasi Celebri...

Se sei abbastanza fortunato di aver vissuto a Parigi come un giovane uomo, allora per il resto della tua vita ovunque andrai, sar? con te, a Parigi ? un continuo banchettare.

Ernest Hemingway 

Sondaggio:

Quale auto per non passare inosservati?

Jaguar
Mercedes
Ferrari
Maserati
Bmw
AlfaRomeo
Bugatti

visualizza risultati


 

Costruttori

L'uso di un metodo Set() per eseguire l'inizializzazione di un oggetto (come mostrato per la struct Complex) e` poco elegante e alquanto insicuro: il programmatore che usa la classe potrebbe dimenticare di chiamare tale metodo prima di cominciare ad utilizzare l'oggetto appena dichiarato. Si potrebbe pensare di scrivere qualcosa del tipo:

 

class Complex {
  public:
    /* ... */

  private:
    float Re = 6;       // Errore!
    float Im = 7;       // Errore!
};

 

ma il compilatore rifiutera` di accettare tale codice. Il motivo e` semplice, stiamo definendo un tipo e non una variabile (o una costante) e non e` possibile inizializzare i membri di una classe (o di una struttura) in quel modo... E poi in questo modo ogni istanza della classe sarebbe sempre inizializzata con valori prefissati, e la situazione sarebbe sostanzialmente quella di prima.
Il metodo corretto e` quello di fornire un costruttore che il compilatore possa utilizzare quando una istanza della classe viene creata, in modo che tale istanza sia sin dall'inizio in uno stato consistente. Un costruttore altro non e` che un metodo il cui nome e` lo stesso di quello della classe, che puo` avere dei parametri, ma che non restituisce alcun tipo (neanche void); il suo scopo e` quello di inizializzare le istanze della classe:

 

Class Complex {
  public:
    Complex(float a, float b) {         // costruttore!
      Re = a;
      Im = b;
    }

    /* altre funzioni membro */

  private:
    float Re;         // Parte reale
    float Im;         // Parte immaginaria
};

 

In questo modo possiamo eseguire dichiarazione e inizializzazione di un oggetto Complex in un colpo solo:

 

Complex C(3.5, 4.2);

 

La definizione appena vista introduce un oggetto C di tipo Complex che viene inizializzato chiamando il costruttore con gli argomenti specificati tra le parentesi. Si noti che il costruttore non viene invocato come un qualsiasi metodo (il nome del costruttore non e` cioe` esplicitamente mensionato, esso e` implicito nel tipo dell'istanza); un sistema alternativo di eseguire l'inizializzazione sarebbe:

 

Complex C = Complex(3.5, 4.2);

 

ma e` poco efficiente perche` quello che si fa e` creare un oggetto Complex temporaneo e poi copiarlo in C, il primo metodo invece fa tutto in un colpo solo.
Un costruttore puo` eseguire compiti semplici come quelli dell'esempio, tuttavia non e` raro che una classe necessiti di costruttori molto complessi, specie se alcuni membri sono dei puntatori; in questi casi un costruttore puo` eseguire operazioni quali allocazione di memoria o accessi a unita` a disco se si lavora con oggetti persistenti.
In alcuni casi, alcune operazioni possono richiedere la certezza assoluta che tutti o parte dei campi dell'oggetto che si vuole creare siano subito inizializzati prima ancora che incominci l'esecuzione del corpo del costruttore; la soluzione in questi casi prende il nome di lista di inizializzazione.
La lista di inizializzazione e` una caratteristica propria dei costruttori e appare sempre tra la lista di argomenti del costruttore e il suo corpo:

 

class Complex {
  public:
    Complex(float, float);
    /* ... */

  private:
    float Re;
    float Im;
};

Complex::Complex(float a, float b) : Re(a), Im(b) { }

 

L'ultima riga dell'esempio implementa il costruttore della classe Complex; si tratta esattamente dello stesso costruttore visto prima, la differenza sta tutta nel modo in cui sono inizializzati i membri dato: la notazione Attributo(< Espressione >) indica al compilatore che Attributo deve memorizzare il valore fornito da Espressione; Espressione puo` essere anche qualcosa di complesso come la chiamata ad una funzione.
Nel caso appena visto l'importanza della lista di inizializzazione puo` non essere evidente, lo sara` di piu` quando parleremo di oggetti composti e di ereditarieta`.
Una classe puo` possedere piu` costruttori, cioe` i costruttori possono essere overloaded, in modo da offrire diversi modi per inizializzare una istanza; in particolare alcuni costruttori assumono un significato speciale:

  • il costruttore di default ClassName::ClassName();
  • il costruttore di copia ClassName::ClassName(ClassName& X);
  • altri costruttori con un solo argomento;

Il costruttore di default e` particolare, in quanto e` quello che il compilatore chiama quando il programmatore non utilizza esplicitamente un costruttore nella dichiarazione di un oggetto:

 

#include < iostream >
using namespace std;

class Trace {
  public:
    Trace() {
      cout << "costruttore di default" << endl;
    }

    Trace(int a, int b) : M1(a), M2(b) {
      cout << "costruttore Trace(int, int)" << endl;
    }

  private:
    int M1, M2;
};

int main(int, char* []) {
  cout << "definizione di B... ";
  MyClass B(1, 5); // MyClass(int, int) chiamato!
  cout << "definizione di C... ";
  MyClass C; // costruttore di default chiamato!
  return 0;
}

 

Eseguendo tale codice si ottiene l'output:

 

definizione di B... costruttore Trace(int, int)
definizione di C... costruttore di default

 

Ma l'importanza del costruttore di default e` dovuta soprattutto al fatto che se il programmatore della classe non definisce alcun costruttore, automaticamente il compilatore ne fornisce uno (che pero` non da` garanzie sul contenuto dei membri dato dell'oggetto). Se non si desidera il costruttore di default fornito dal compilatore, occorre definirne esplicitamente uno (anche se non di default).

Il costruttore di copia invece viene invocato quando un nuovo oggetto va inizializzato in base al contenuto di un altro; modifichamo la classe Trace in modo da aggiungere i seguente costruttore di copia:

 

Trace::Trace(Trace& x) : M1(x.M1), M2(x.M2) {
  cout << "costruttore di copia" << endl;
}

 

e aggiungiamo il seguente codice a main():

 

cout << "definizione di D... ";
Trace D = B;

 

Cio` che viene visualizzato ora, e` che per D viene chiamato il costruttore di copia.
Se il programmatore non definisce un costruttore di copia, ci pensa il compilatore. In questo caso il costruttore fornito dal compilatore esegue una copia bit a bit (non e` proprio cosi`, ma avremo modo di vederlo in seguito) degli attributi; in generale questo e` sufficiente, ma quando una classe contiene puntatori e` necessario definirlo esplicitamente onde evitare problemi di condivisione di aree di memoria.
I principianti tendono spesso a confondere l'inizializzazione con l'assegnamento; benche` sintatticamente le due operazioni sono simili, in realta` esiste una profonda differenza semantica: l'inizializzazione viene compiuta una volta sola, quando l'oggetto viene creato; un assegnamento invece si esegue su un oggetto precedentemente creato. Per comprendere la differenza facciamo un breve salto in avanti.
Il C++ consente di eseguire l'overloading degli operatori, tra cui quello per l'assegnamento; come nel caso caso del costruttore di copia, anche per l'operatore di assegnamento vale il discorso fatto nel caso che tale operatore non venga definito esplicitamente. Il costruttore di copia viene utilizzato quando si dichiara un nuovo oggetto e si inizializza il suo valore con quello di un altro; l'operatore di assegnamento invece viene invocato successivamente in tutte le operazioni che assegnamo all'oggetto dichiarato un altro oggetto. Vediamo un esempio:

 

#include < iostream >
using namespace std;

class Trace {
  public:
    Trace(Trace& x) : M1(x.M1), M2(x.M2) {
      cout << "costruttore di copia" << endl;
    }

    Trace(int a, int b) : M1{a), M2(b) {
      cout << "costruttore Trace(int, int)" << endl;
    }

    Trace & operator=(const Trace& x) {
      cout << "operatore =" << endl;
      M1 = x.M1;
      M2 = x.M2;
      return *this;
    }

  private:
      int M1, M2;
};

int main(int, chra* []) {
  cout << "definizione di A... " << endl;
  Trace A(1,2);
  cout << "definizione di B... " << endl;
  Trace B(2,4);
  cout << "definizione di C... " << endl;
  Trace C = A;
  cout << "assegnamento a C... " << endl;
  C = B;
  return 0;
}

 

Eseguendo questo codice si ottiene il seguente output:

 

definizione di A... costruttore Trace(int, int)
definizione di B... costruttore Trace(int, int)
definizione di C... costruttore di copia
assegnamento a C... operatore =

 

Restano da esaminare i costruttori che prendono un solo argomento.
Essi sono a tutti gli effetti dei veri e propri operatori di conversione di tipo(vedi appendice A) che convertono il loro argomento in una istanza della classe. Ecco una classe che fornisce diversi operatori di conversione:

 

class MyClass {
  public:
    MyClass(int);
    MyClass(long double);
    MyClass(Complex);
    /* ... */

  private:
    /* ... */
};

int main(int, char* []) {
  MyClass A(1);
  MyClass B = 5.5;
  MyClass D = (MyClass) 7;
  MyClass C = Complex(2.4, 1.0);
  return 0;
}

 

Le prime tre dichiarazioni sono concettualmente identiche, in tutti e tre i casi convertiamo un valore di un tipo in quello di un altro; il fatto che l'operazione sia eseguita per inizializzare degli oggetti non modifica in alcun modo il significato dell'operazione stessa.
Solo l'untima dichiarazione puo` apparentemente sembrare diversa, in pratica e` comunque la stessa cosa: si crea un oggetto di tipo Complex e poi lo si converte (implicitamente) al tipo MyClass, infine viene chiamato il costruttore di copia per inizializzare C. Per finire, ecco un confronto tra costruttori e metodi (o normali funzioni) che riassume quanto detto:

 

  Costruttori Metodi
Tipo restituito nessuno qualsiasi
Nome quello della classe qualsiasi
Parametri nessuna limitazione nessuna limitazione
Lista di inizializzazione si no
Overloading si si

 

Altre differenze e similitudini verranno esaminate nel seguito.

 

 

successivo
–«  INDICE  »–

 

 

 

 
Powered by paper&pencil (carta&matita ) - Copyright © 2001-2022 Cataldo Sasso