Inizializzazione, Assegnamento e Distruzione di Classi Lezione 9 Operazioni Automatiche In ogni programma C++ oggetti classe vengono gestiti automaticamente dal compilatore Inizializzati al momento della definizione Assegnati ad altri oggetti della stessa classe Convertiti in oggetti di altro tipo Distrutti al termine del loro tempo di vita Queste operazioni sono eseguite automaticamente dal compilatore invocando metodi della classe Il progettista della classe deve specificare come queste operazioni devono essere eseguite 1 2001/02 1
Inizializzazione L operazione di inizializzazione viene invocata subito dopo che il compilatore ha allocato la memoria per un oggetto della classe Assegna valori iniziali a ciascuno dei membri dato dell oggetto Se i membri dato sono pubblici possono essere inizializzati esplicitamente (come per uno struct) class Dato { public: int ival; char* ptr; ; Dato d = {1204, asd ; Dato d2 = { asd, 1024); // ERRORE 2 Costruttore Il meccanismo di inzializzazione più usato è il costruttore Metodo della classe invocato automaticamente dal compilatore al momento dell inizializzazione Può inizializzare anche i membri dato privati Il costruttore è un metodo della classe Ha lo stesso nome della classe Non ha tipo del risultato Può essere sovrapposto Non può essere definito sicuro 3 2001/02 2
Utilizzo di un Costruttore class ContoCorrente { public: ContoCorrente( ); ContoCorrente( const char*, double, int); private: char* _nome; unsigned int _numerocc; double _saldo; ; ContoCorrente miocc( auletta, 0, 123456); /* alloca la memoria per miocc e invoca la funzione ContoCorrente::ContoCorrente per inizializzarla */ 4 Sovrapposizione di Costruttori Una classe può avere più costruttori per consentire diversi tipi di utilizzo degli oggetti Uno o più costruttori riservati agli utenti della classe Un costruttore riservato agli amici della classe class ContoCorrente { friend class Banca; public: ContoCorrente( const char*, double = 0); private: ContoCorrente(const char*, int, double = 0); char* _nome; unsigned int _numerocc; double _saldo; ; ContoCorrente miocc( auletta ); 5 2001/02 3
Esempi di uso di un costruttore ContoCorrente miocc1( Blundo ); // Definisco un oggetto di tipo ContoCorrente in cui _nome è // inizializzato con Blundo e _saldo con 0.0 // _numerocc dipenderà dall implementazione del costruttore ContoCorrente suocc( Blundo,1234.5); // come prima ma _saldo è inizializzato a 1234.5 ContoCorrente miocc2( Blundo,0,1234.5); // Errore // ContoCorrente(const char*, int, double = 0); è privato 6 Invocazione dei Costruttori I costruttori vengono invocati ogni volta che viene allocata della memoria ad un oggetto della classe Definizione di oggetti della classe Passaggio per valore di oggetti ad una funzione Creazione di un oggetto temporaneo Allocazione dinamica di un oggetto Nel caso dell allocazione dinamica il costruttore viene invocato solo se l allocazione riesce 7 2001/02 4
Invocazione dei Costruttori Ci sono diverse forme per specificare gli argomenti da passare al costruttore ContoCorrente miocc( auletta); ContoCorrente miocc = ContoCorrente( auletta ); ContoCorrente miocc = auletta ; /* utilizzabile solo se il costruttore prende un solo argomento */ ContoCorrente *ptrcc = new ContoCorrente( auletta ); 8 Costruttore di Default Il costruttore di default consente di inizializzare oggetti della classe senza passare argomenti al costruttore Il costruttore di default viene invocato quando non viene specificate la lista degli argomenti per il costruttore ContoCorrente miocc; ContoCorrente miocc(); // Invoca il costruttore di default // ERRORE È possibile definire oggetti senza specificare argomenti per il costruttore solo se non ci sono costruttori o c è un costruttore di default 9 2001/02 5
Array di Oggetti di Classe Un array di oggetti di classe è definito normalmente Nella lista di inizializzazione vengono specificati gli argomenti per i costruttori È possibile definire inizializzazioni parziali solo se la classe ha un costruttore di default ContoCorrente listacc[10] = { baggio, del piero, ContoCorrente( totti, 1000000); Se si alloca dinamicamente un array di oggetti di classe non si possono passare argomenti ai costruttori La classe deve avere il costruttore di default oppure non deve avere costruttori 10 Vettori di Oggetti di Classe È possibile definire vettori di oggetti di una classe vector<contocorrente> archivio(5); L inizializzazione del vettore avviene secondo questi passi Viene creata una variabile temporanea di tipo ContoCorrente inizializzata per default Vengono inizializzati per copia tutti gli elementi del vettore Viene distrutta la variabile temporanea L inizializzazione di un vettore è meno efficiente di quella di un array Conviene creare un vettore vuoto ed inserire gli elementi uno per volta 11 2001/02 6
Costruttori explicit Ogni costruttore che prende un solo argomento opera come funzione di conversione Utilizzato per creare oggetti temporanei della classe nella valutazione di espressioni Se si vuole impedire che un costruttore sia usato per effettuare conversioni lo si può qualificare explicit Un costruttore explicit viene usato per effettuare conversioni solo se esplicitamente invocato dal programma 12 Esempio void f(const ContoCorrente& cc); char* s= Zoff ; f(s); // equivale a: // ContoCorrente tmp(s); // f(tmp); 13 2001/02 7
Costruttori per Copia È sempre possibile inizializzare un oggetto di una classe con un altro oggetto della stessa classe Inizializzazione membro a membro di default Inizializza ogni membro dato con il corrispondente dato dell oggetto usato per l inizializzazione In molte situazioni la semantica dell inizializzazione membro a membro non corrisponde alle esigenze della classe Per la classe ContoCorrente l oggetto inizializzato avrebbe lo stesso numero di conto dell oggetto usato per inizializzarlo È possibile ridefinire la semantica definendo un costruttore per copia 14 Costruttori per Copia Il costruttore per copia prende un solo argomento per riferimento costante ContoCorrente::ContoCorrente(const ContoCorrente& cc) { _nome = new char[strlen(cc._nome)+1]; strcpy(_nome, cc._nome); _saldo = cc._saldo; _numerocc = _genera_numero_cc(); ContoCorrente miocc( auletta, 1000); ContoCorrente newcc(miocc); //si usa il costruttore per copia ContoCorrente newcc2 = miocc; //si usa il costruttore per copia Il costruttore per copia, se esiste, scavalca l inizializzazione membro a membro di default Se esiste ma è privato il compilatore da errore 15 2001/02 8
Distruttore di una Classe Tra i compiti di un costruttore c è quello di assegnare ad un oggetto appena allocato le risorse che gli occorrono Memoria dinamica, file, periferiche Prima di terminare il suo tempo di vita un oggetto deve rilasciare le risorse che gli sono state assegnate Il distruttore è un metodo speciale che provvede alla deallocazione delle risorse Viene automaticamente invocato prima che un oggetto venga distrutto 16 Distruttore di una Classe Il distruttore è un metodo speciale Il nome è uguale al nome della classe, preceduto da ~ Non prende argomenti Non restituisce risultato Non può essere sovrapposto ContoCorrente::~ContoCorrente() { delete [] _nome; restituisci_numero_cc(_numerocc); Non tutte le classi necessitano di un distruttore Il distruttore non serve a deallocare la memoria assegnata ai membri dato Il distruttore deallocaoggetti referenziati da membri dato 17 2001/02 9
Distruttori e Debugging Spesso si usa il distruttore per facilitare il debugging del programma Tiene traccia dello stato dei membri dato al momento in cui l oggetto cessa di esistere Specifica quando gli oggetti escono dal proprio scope 18 Inizializzazione di Membri Dato di Classe Se una classe ha un membro dato che è un oggetto di una classe X deve invocare il costruttore di X per inizializzare il suo membro Se la classe X non ha un costruttore di default si devono necessariamente passare degli argomenti al costruttore della classe ContoCorrente X { public: ContoCorrente( const string&, double, int); ContoCorrente(); ContoCorrente(const ContoCorrente&); private: string _nome; ; unsigned int _numerocc; double _saldo; /* per inizializzare _nome si deve invocare il costruttore di string */ 19 2001/02 10
Inizializzazione di Membri Dato di Classe La presenza di un membro dato di tipo X (classe) presenta tre problemi Come si invoca il costruttore di default di X dall interno del costruttore di default della classe Come si invoca il costruttore per copia di X dall interno del costruttore per copia della classe Come si passano gli argomenti al costruttore di un membro dato 20 Lista di Inizializzazione Ogni costruttore ha una lista di inizializzazione Utilizzata per inizializzare ogni membro della classe La lista è tra la lista dei parametri della funzione ed il corpo della funzione, ed è preceduta da : La lista è formata da una sequenza di coppie separate da, Ogni coppia specifica il nome di un membro ed i valori con cui deve essere inizializzato ContoCorrente::ContoCorrente(const string& s, double valore) : _nome(s), _saldo(valore) { _numerocc = _genera_numero_cc(); 21 2001/02 11
Lista di Inizializzazione Tutti i costruttori usano la lista di inizializzazione per inizializzare i membri della classe Tutte le operazioni fatte nel corpo del costruttore sono assegnamenti Se un membro non viene specificato nella lista viene inizializzato tramite il costruttore di default Tutti i membri della classe dovrebbero essere inizializzati tramite la lista di inizializzazione Devono essere necessariamente inizializzati tramite la lista di inizializzazione Oggetti i cui costruttori hanno bisogno di argomenti Costanti 22 Lista di Inizializzazione Ogni membro deve comparire una sola volta nella lista L ordine delle coppie nella lista di inizializzazione è ininfluente I membri dato vengono inizializzati nell ordine in cui sono stati dichiarati nella definizione della classe Ciò può essere fonte di errori!! 23 2001/02 12
Cosa non va? class X { int i; int j; public: X(int val) : j(val), i(j) { // L intenzione del programmatore è i = j = val // vioene eseguito i = j; j = val; // ; Se si deve inizializzare un membro di una classe con il valore di un altro membro della stessa classe usare l assegnamento nel corpo del costruttore 24 Assegnamento È sempre possibile assegnare un oggetto di una classe ad un altro oggetto della stessa classe L operazione è gestita da un operatore di assegnamento membro a membro di default Simile all inizializzazione membro a membro di default ContoCorrente& ContoCorrente::operator=(const ContoCorrente& cc) { _nome = cc._nome; _saldo = cc._saldo; _numerocc = cc._numerocc; return *this; Ha gli stessi problemi dell inizializzazione membro a membro di default Deve restituire un risultato di tipo ContoCorrente 25 2001/02 13
Operatore di Assegnamento L operatore di assegnamento deve controllare gli autoassegnamenti Evita di perdere tempo a copiare un valore su se stesso Garantisce che l oggetto da cui stiamo copiando non sia stato deallocato L operatore di assegnamento avrà sempre la seguente forma: NomeClasse& NomeClasse::operator=(const NomeClasse& nc) { if( this!= &nc ) { // Effettua la copia return *this; 26 Esempio di Operatore di Assegnamento ContoCorrente& ContoCorrente::operator=(const ContoCorrente& cc) { if( this!= &cc ) { _nome = cc._nome; _saldo = cc._saldo; _numerocc = _genera_numero_cc(); return *this; 27 2001/02 14
Costruttori da inserire nelle classi Costruttore di default NomeClasse(); Costruttore per copia NomeClasse(const NomeClasse&); Costruttore per inizializzazione NomeClasse( parametri di inizializzazione ); 28 2001/02 15