< < indice degli appunti sul linguaggio C++

Nel linguaggio C++ si utilizzano dei “modelli” chiamati classi. Per capire cosa sia una classe e una funzione membro si deve prima leggere

Dato che la classe è un nuovo tipo di dato, essa deve descrivere, oltre alla struttura dei dati e delle sue funzioni membro, anche come (1)riservare, (2)inizializzare e infine (3)liberare la memoria occupata da ogni oggetto di tale classe. Il costruttore serve proprio per le prime due esigenze: riservare ed inizializzare la memoria. La liberazione, invece, è fatta dal distruttore.

Il costruttore è una funzione che permette di realizzare (creare) gli oggetti in memoria, di un certo tipo di dato (la classe). Il compilatore permette di usare un costruttore di default (predefinito), ma anche di dichiarare diversi costruttori personalizzati che differiscono dal primo solo per i parametri che gli vengono passati.

Dichiarazione

La dichiarazione costruttore assomiglia a quella di una normale funzione membro, ma a differenza di questa:

  1. non restituisce nulla (neppure void);
  2. possiede lo stesso nome della classe a cui appartiene.

Codice della chiamata del costruttore predefinito, per creare l'oggetto conto1

ContoCorrente conto1;

Il codice appena visto deve essere inserito nel contesto di questo esempio: esempio_di_programmazione_orientata_agli_oggetti

Codice della chiamata di un altro costruttore, con qualche argomento, per creare conto2 con un saldo iniziale di 90 euro.

ContoCorrente conto2(90.00);

Definizione

Il costruttore costruisce l'oggetto nella memoria costruendo prima i dati membri, nell'ordine in cui sono dichiarati nella classe, e eseguendo per ultimo il codice del costruttore vero e proprio (il distruttore procede in ordine inverso). Per evitare confusione è utile usare lo stesso ordine usato per la dichiarazione dei membri, anche nella dichiarazione dei parametri del costruttore.

ContoCorrente::ContoCorrente(int saldo)  // definizione del costruttore
{
   mSaldo = saldo;  // inizializzazione dei membri dell'oggetto tramite assegnazione
                    // questo metodo è inefficiente :(
       // resto del codice...
}

La lista di inizializzazione dei membri

C'è un modo molto più comodo ed efficiente di inizializzare i membri di un oggetto mettendo anche in evidenza l'ordine di inizializzazione dei membri appena descritto, ed è la cosiddetta lista di inizializzazione, in cui l'inizializzazione dei membri non viene fatta con un'assegnazione nel corpo del costruttore, ma prima delle parentesi graffe, utilizzando i due punti (:).

ContoCorrente::ContoCorrente(int saldo)    // definizione del costruttore
       : mSaldo(saldo)                     // inizializzazione tramite lista di inizializzazione
{
    // resto del codice...
}

Questa tecnica, alternativa alle assegnazioni, non è obbligatoria per i tipi primitivi (come int) ma è comunque consigliata. Diventa indispensabile usarla per i seguenti tipi (che non possono rimanere non inizializzati):

  • per i membri costanti,
  • per tutti i membri che non usano il costruttore predefinito (oggetti)
  • per tutti i reference.

Usandola sempre si evita di sbagliare!

Al momento della chiamata del costruttore, prima verranno eseguiti tutti gli inizializzatori, e, dopo, il codice del costruttore.

Sono funzioni che esistono sempre, anche se il programmatore non le definisce. In questo modo si ottiene la garanzia che alcune operazioni di uso frequente siano possibili. Si tratta di operazioni necessarie all'allocazione/inizializzazione/distruzione di oggetti:

  • costruttore minimo
    NomeClasse();
  • distruttore
    ~NomeClasse();
  • costruttore di copia
    NomeClasse (const NomeClasse&);
  • operazione di assegnazione con copia
    NomeClasse& operator= (const NomeClasse& o)

Oltre ad essere operazioni frequenti, si tratta anche di operazioni delicate, che spesso il programmatore deve ri-definire, per ottenere il risultato che desidera. Solo per fare alcuni esempi, il costruttore di copia viene usato quando:

  • si passa un oggetto per valore ad una funzione
  • una funzione deve restituire un oggetto
  • una funzione deve lanciare un oggetto come eccezione (throw)

Tutte le funzioni membro viste, essendo predefinite, non vengono nemmeno ereditate. polimorfismo

Costruttore predefinito

Il costruttore predefinito è usato per l'allocazione e l'inizializzazione dei dati membro di un oggetto (che si potrebbe chiamare a). Può essere invocato esplicitamente dal programmatore oppure automaticamente dall'operatore new.

NomeClasse a;    // costruttore predefinito : privo di argomenti!

È utile definirlo quando si crea una nuova classe in una libreria, perché chi utilizzerà la libreria potrebbe creare un oggetto senza fornire adeguati elementi di inizializzazione ed un eventuale costruttore predefinito fornirebbe una certa garanzia sul contenuto del nuovo oggetto. Inoltre il costruttore predefinito può essere riutilizzato all'interno degli altri costruttori, fornendo la garanzia della funzionalità e di non dover riscrivere due volte le stesse cose…

  • dichiarazione del costruttore predefinito
    •     NomeClasse ();
  • definizione
    •     NomeClasse::NomeClasse ()
            : // segue la lista di inizializzazione...
          {}

Distruttore predefinito

~NomeClasse(); // deve essere privo di argomenti

È molto simile al precedente e viene invocato automaticamente quando un oggetto precedentemente creato esce dal suo campo di visibilità o deallocato con delete. Quindi a maggior ragione è importante che esista sempre. Va ricordato che il distruttore non ha mai nessun argomento.

  • dichiarazione del distruttore
    •     ~NomeClasse ();
  • definizione
    •     NomeClasse::~NomeClasse ()
             {}

Costruttore di copia predefinito

  • Il costruttore predefinito è usato per l'inizializzazione dei dati membro di un oggetto (a).
  • Anche il costruttore di copia predefinito inizializza (usando i dati membro di un altro oggetto esistente).
NomeClasse a();    // costruttore predefinito
NomeClasse b = a;  // costruttore di copia predefinito

Nel caso in cui un oggetto (a) contiene almeno un puntatore ad un altro oggetto (c), debba essere duplicato (b), il costruttore di copia predefinito effettuerebbe solo una copia dei valori contenuti nei dati membro e verrebbero solo copiati gli indirizzi contenuti nei puntatori. Esisterebbero due nuovi oggetti (a e b), ma il secondo contiene dei puntatori allo stesso oggetto (c) a cui punta il primo e modificando il contenuto del secondo (b) si rischia di modificare il contenuto del primo (a). Per questo motivo il costruttore di copia deve essere ridefinito nel caso che la classe abbia come membri: reference, costanti o altri oggetti di tipo classe. Questo perché tutti questi tipi non sono mai inizializzate automaticamente, a meno che siano di tipo static

  • dichiarazione del costruttore con copia
    •     NomeClasse (const NomeClasse&);
  • definizione
    •     NomeClasse::NomeClasse (const NomeClasse& o)
            : // segue la lista di inizializzazione...
          {}

Il costruttore di copia viene usato dal programma sia nel passaggio di parametri per valore sia nel momento della restituzione di un valore alla funzione chiamante. Perciç questo costruttore dovrebbe essere personalizzato dal programmatore. Durante il passaggio di parametri per valore ad una funzione, il programma deve usare il costruttore di copia predefinito per poter realizzare una copia degli oggetti passati. È bene comunque ricordare che in alternativa al passaggio per valore si potrebbe passare l'oggetto usando un reference costante. Questo eviterebbe anche l'allocazione di nuova memoria…

Operatore assegnazione di copia predefinito

  • Il costruttore predefinito è usato per l'inizializzazione dei dati membro di un oggetto (a).
  • Anche il costruttore di copia predefinito inizializza (usando i dati membro di un altro oggetto esistente).
  • L'assegnazione tra oggetti dello stesso tipo, dà luogo invece ad una copia tra oggetti esistenti.
NomeClasse a();    // costruttore predefinito
NomeClasse b = a;  // costruttore di copia predefinito
NomeClasse c;
c = b;         //assegnazione di copia predefinita

Nonostante vi sia una differenza sostanziale, l'operazione di assegnazione opera in modo simile al costruttore di copia, dovendo inizializzare tutti i dati membro di a, in base a quelli di b. Per tale motivo esso deve essere ridefinito nel caso che la classe abbia come membri: reference, costanti o altri oggetti di tipo classe. Questo perché tutti questi tipi non sono mai inizializzate automaticamente, a meno che siano di tipo static

  • dichiarazione dell'operatore assegnazione con copia
    •     NomeClasse& operator= (const NomeClasse& o)
  • definizione
    •     NomeClasse& NomeClasse::operator= (const NomeClasse& o)
          {
           //...
           return *this;
          }
  • appunti3s/costruttore.txt
  • Last modified: 2018/04/25 07:55
  • (external edit)