Sei sulla pagina 1di 9

Lezione 2 – 28/09

I namespace si possono annidare uno dentro l’altro. Alias di namespace serve nel caso avessi namespace
con nomi troppo lunghi. Si fa come si vede nella slide.
Direttiva d’uso (using namespace x) leva la protezione del namespace; cioè tutti i nomi confinati in quel
namespace sono accessibili. Quindi using namespace std in genere non si fa.
:: è il dichiaratore d’uso. Quindi per usare cout, cin, ecc. faccio std::cout… << e >> sono operatori
sovraccaricati, si vedrà più avanti.
Gcc compilatore di Linux e Windows; clang su Mac.
Se invoco size su una stringa lui mi restituisce un intero che mi indica il numero di caratteri usati per quella
stringa.
Programmazione ad oggetti deve permettere di separare interfaccia e implementazione (incapsulamento).
Class è semanticamente molto vicina a struct; la differenza è la vicinanza ai tipi di dati.
Class e private sono keyword; int lo sarebbe pure ma alla fine è un tipo di dato primitivo. Dopo a public ci
sono i membri accessibili all’esterno; infatti ore, minuti e secondi sono funzioni. Nella parte privata c’è la
dichiarazione della funzione, che voglio non sia accessibile all’utente.
Parametro this puntatore che punta all’oggetto; è un parametro implicito.

Lezione 3 – 29/09
Interfaccia della classe = la lista dei membri pubblici. Non è cosa buona lasciare accesso ai campi dati
all’esterno della classe.
Non ci basta che il codice funzione; il codice deve aderire ai principi della programmazione ad oggetti (in
particolare l’information hiding).
Potrei mettere dei membri al di fuori di public e private; in questo caso, il livello di accesso di default è
private. In particolare, questa è la vera differenza tra class e struct: nella seconda il livello di accesso di
default è public.
È importante scegliere bene i nomi pubblici.
Dichiarazione di una variabile oggetto significa chiamare il costruttore di default.
Overloading si può fare perché le funzioni differiscono da numero di parametri e/o dal tipo dei parametri
(caso ambiguo potrebbe essere esempio come una f con int, double, l’altra con double, int; in questo caso
non compila).
Negli esempi con l’orario il costruttore di default costruisce la mezzanotte.
Anonimo: non ha un nome, e quindi non è indirizzabile (tradotto non ha un L-valore).
Heap sotto controllo del programmatore: alloco memoria con new e dealloco con delete.
Costruttore standard è disponibile se la classe non ne prevede altri. Ogni classe deve avere un costruttore di
default; se ci fossero costruttori definiti dall’utente ma non quello di default, succedono casini.

Lezione 4 – 01/10
Se un parametro ha un valore di default devono averli anche i parametri a destra. Nella dichiarazione i
valori di default si mettono, nella definizione (quando la invoco) non li ripeto.
Explicit: marcatura del costruttore; blocca la conversione.
In c++ si distinguono nettamente funzioni che possono o no invocare lo stato dell’oggetto di invocazione
(fanno side effects); nella slide di esempio, void avanzaunora ha side effects, orario unorainpiu no (e lo
capisci dal tipo della funzione, cioè quello che ritornerà).
Orario unorapiutardi prende l’oggetto di invocaizione, in lettura, e lo usa per fare aux (cioè l’oraio
ausiliario). Un metodo che certamente non fa side effects si chiama costante e bisogna marcarlo mettendo
const (alla fine).
Se marco come const un metodo non posso invocare all’interno di questo metodo metodi non costanti.
This è puntatore a oggetto di invocazione.
Const int: deve necessariamente essere inizializzata (è costante, che senso ha se non le do un valore), non
può essere usata come L-valore (ovviamente, non potendo essere modificata). Negli oggetti, un oggetto
marcato costante non può essere invocato da un metodo non costante.
In conclusione: se c’è const, sicuramente il metodo non farà side effects, se non c’è può farli. E i metodi
vanno indicati obbligatoriamente in base a questa regola con const o no.

Lezione 5 – 04/10
Su un oggetto non costante posso invocare sia funzioni costanti che non costanti. Avere come tipo di
ritorno un tipo per valore ti dice che il valore ritornato non è dotato di indirizzo.
Const va alla fine della dichiarazione della funzione, static all’inizio. Le funzioni non statiche si chiamano
anche “metodi di istanza”, perché usano l’istanza della classe. Il metodo marcato static quindi si invoca
senza oggetto di invocazione.

Lezione 6 – 05/10
Se un attributo della classe è statico e viene usato in sola lettura si mette il const. In questo caso viene
inizializzato e non si può più modificare (per via del const).
Overloading si indica sempre come operator e il nome di quell’operatore (tipo operator+). Overloading
(sovraccaricamento) vuol dire modificare il senso dell’operatore.
[] operatore di subscripting (indicizzazione). Sì è un operatore. () chiamata di funzione.
Regola 2: tra gli operandi almeno uno è tipo definito da utente, perché sennò si potrebbe cambiare il senso
di somma tra interi, per esempio. È quindi una regola che viene da sé.
Il valore ritornato dall’operatore ++ (postfisso) è il valore prima dell’incremento: infatti e++ ritorna 4 e non
5, perché memorizza il valore di e prima dell’incremento.

Lezione 7 – 06/10
Un array di dimensione statica altro non è che un puntatore costante, quindi non posso modificarlo.
(int A[10]; int B[10]; A = B; non si può fare). Gli array non hanno memoria condivisa; posso fare assegnazioni
nel caso in cui un oggetto abbia come campo un array, e in quel caso viene assegnata cella per cella.
Ostream lo passo per riferimento non costante e lo ritorno per riferimento per permettere le invocazioni
multiple (a cascata).
Se marco const un metodo, quel this avrà tipo const C*, quindi il parametro puntato acquisisce il const.

Lezione 8 – 08/10
La prima cosa che fa il compilatore appena lo lancio è avviare il preprocessore. #define definisce delle
macro. Ci saranno decine di file .h e decine di file .cpp. #include con <> va a cercare quel file nei folder di
riferimento del compilatore.
#ifdef = se questa macro è già stata deifinita allora faccio il testo fino a #endif; #ifndef sarà il contrario.
Ogni file .h ha una corrispondente macro, che è una costante; la prassi per definirla è di mettere il nome in
maiuscolo e poi al posto di .h si mette _H.
Ci sono dei tool che permettono di fare la compilazione automatizzata; d’altronte, tentare di fare la
compilazione a mano con tutti i casini di file (con dipendenze tra loro) diventa ingestibile. Quindi esiste il
comando make; che è un comando mica banale, e bello potente.
Tipo non classe = non definito dall’utente.
Se definisco dei costruttori quello di default non esiste più; quindi devo preoccuparmi io di definirlo.
&& vs &: && è lazy and, perché se controlla condizioni e la prima è falsa le altre non le controlla; & invece
va avanti a controllarle. Stessa storia per | vs ||. (& e |, andando avanti a controllare, potrebbero causare
una non terminazione del programma).
Nell’es per casa richiede di rappresentare il numero in maniera migliore che come int. Potremmo usare una
string o un array di char, ma la soluzione migliore è quello di farne un oggetto (risolvo il problema da
un’altra parte).

Lezione 9 – 11/10
Normalmente con i tipi primitivi si evita di passarli con const, semplicemente perché spesso costa meno
Byte passarli per valore senza il const (col const si aggiungono Byte).
Se ho campi dati di tipo riferimento devono avere un coso che li indirizza subito a qualcosa di già
inizializzato, altrimenti non ha senso.
In un costruttore di copia ridefinita tutti i parametri compaiono nel suo campo dati.

Lezione 10 – 12/10
operator++ () si riferisce a ++x; operator++(int) si riferisce a x++. È una cosa sintattica (int non è un
parametro) e bisogna ricordarsela: quindi prefisso senza e postfisso con. La differenza tra i due è il tipo di
ritorno: x++ ritorna per valore, ++x per riferimento. Quindi ++x ritorna per riferimento il valore dopo
l’incremento; x++ ritorna per valore il valore prima dell’incremento (per forza, non può fare altrimenti).
Ricorda che l’operatore di output andrà sempre definito fuori dalla classe.
Classi annidate: senso di distinguere in parte privata e pubblica all’interno della parte privata:
disaccoppiamento del codice, che è un grosso vantaggio soprattutto quando il codice è sviluppato da più
persone.
Nei puntatori, nullptr è preferibile a 0 perché il primo è nativamente di tipo *ptr. Golden rule: prima di
dereferenziale un puntatore, assicurarsi che non sia null.

Lezione 11 – 13/10
Spesso negli esami c’è qualcosa da fare a livello profondo.
Prima di assegnare pensa a dove puntava quella variabile puntatore (problema del first). Nel secondo
tentativo di scrivere operator= il bug si presenta facendo b=b, e ciò che succede è che b viene distrutto.
Quindi la versione finale, e quella da ricordare perché c’è nei compiti, è la terza. Pattern: controllo che non
è x=x, faccio pulizia, faccio la copia, ritorno.
Quando stiamo lavorando in maniera profonda, passare per valore vuol dire fare copia profonda.

Lezione 12 – 15/10
Tempo di vita dell’oggetto non per forza coincide con il tempo di vita di una variabile che gli fa
puntatore/riferimento.
In Pao le variabili globali non servono a niente. Nel caso venissero usate, verrebbero memorizzate nella
memoria statica, come le variabili statiche (la memoria statica è una zona specifica della memoria); e come
le statiche vivono fino alla terminazione del programma.
All’interno di una funzione ricorsiva, una variabile statica potrebbe servire per far comunicare tra loro le
diverse chiamate ricorsive.
Variabili dinamiche sono memorizzate nello heap, e la loro vita dipende (ovviamente) dal programmatore.
Il distruttore si occupa di deallocare un oggetto. Come il costruttore standard e il costruttore di copia può
essere overloadato. Un esempio in cui il distruttore standard non va bene è il caso in cui field della classe
siano puntatori (deallocherebbe solo il puntatore, ma io voglio che deallochi tutta la memoria puntata).
Il distruttore standard di nodo per il next dealloca il puntatore, per il campo telefonata invoca il distruttore
di telefonata; questo a sua volta invocherà il distruttore di orario, dove c’è un primitivo int e verrà
deallocato. Quindi delete next è effettivamente una chiamata ricorsiva. E attenzione che all’interno del
corpo del distruttore ridefinito viene comunque effettuata la distruzione standard (nell’esempio
distruggi(first)).
Variabili locali eliminate all’inverso dell’ordine di costruzione. Nel call stack le chiamate di distruzione
quindi vengono impilate una sopra l’altra.
Nell’array dinamico si sceglie la dimensione a runtime, quindi il compilatore alloca solo il puntatore alla
prima cella. Con questi array sono responsabile della loro distruzione: si usa delete[] arrayDinamico
(arrayDinamico sia il nome dell’array).

Lezione 13 – 18/10
C_handle serve per nascondere ulteriormente i membri privati della classe: e si fa “inscatolando” una classe
che contiene i membri privati. Class C_privata è un tipo di dato annidato all’interno di C_handle. È una
dichiarazione incompleta perché non c’è la dichiarazione dei membri; al suo posto c’è il puntatore a questi
oggetti. Cambia solo che piuttosto che this->sec si usa punt->sec; invece per le funzioni che usano solo la
parte pubblica non cambia nulla.
Per risolvere il problema dell’interferenza bisogna ridefinire copia, costruttore e distruttore in maniera
profonda.
Ad ogni modo tutta sta roba dell’hiding dei membri privati non viene usato mai, perciò è solo un esercizio.
C++ 2003: parte privata di classe annidata inaccessibile da quella fuori e viceversa. Ciò che comportava era
che si concedevano amicizie a iosa. Secondo Ranzato è corretto che la classe contenitrice non possa
accedere ai membri privati della classe contenuta, ma che invece la classe annidata possa accedere a quelli
della classe contenitrice. Ed è quello che hanno fatto in C++11.
Iteratore: fornire funzionalità di alto livello per scorrere e accedere a elementi di una collezione. Modifiche
sul contenitore molto spesso si ripercuotono sull’iteratore. Tipicamente un iteratore è rappresentato
tramite un puntatore.
Di sicuro con un iteratore devo sapere se due iteratori sono uguali o no, quindi in public troviamo
l’overloading di == e !=. Un iteratore deve vivere annidata all’interno della sua classe di riferimento. Se sono
annidato nella parte privata posso permettermi di lasciare i membri pubblici, se sono nella parte pubblica
devo stare attento a proteggere i necessari rendendoli privati.
La classe iteratore non ha costruttori, le vengono offerti dalla classe contenitore. Iteratore end() non è
l’ultimo, è quello che ottengo facendo end++, infatti è chiamato “past di end”.
Operatore di subscript è un operatore binario. Ridefinire l’operatore di subscript usando l’iteratore si può
fare ma non è lo standard.

Lezione 14 – 19/10
Nell’esempio della classe puntatore, punt è const, ma ovviamente il nodo puntato da punt non è const.

Lezione 15 – 20/10
Fare copia profonda sempre è l’idea migliore? No. Noi vorremmo condividere la memoria in maniera sicura
e controllata. Questione del reference counting. Il garbage collector in fin dei conti va sullo heap e guarda
chi ha il reference count a 0 e li leva. Quando non c’è come in c++ bisogna farlo manualmente. Però si può
fare implementando un puntatore smart: ha al proprio interno un puntatore con capacità superiori, ma che
permette all’utente di utilizzarlo come un puntatore ordinario.
Comunque da c++11 è stato implementato come tipo da libreria.
Void*: puntatori generici.
T& chiede necessariamente che l’espressione sia indirizzabile, T no; quindi se è T& non posso ritornare un
temporaneo, però quello che può succedere è che ritorni per valore. Questo può succedere perché c’è la
conversione implicita.
Un puntatore si porta dietro un indirizzo, la size del tipo e il tipo (come leggere il tipo). Un puntatore
generico void* serve a qualcosa forse.
Unsigned int => int e double => int si possono fare ma vanno confermate dal programmatore:
static_cast<argomento_di_conversione>(chi_converto)
Lezione 16 – 22/10
Secondo Ranzato il template è come uno stampino per biscotti; template come generatore di codice.
In C i template non ci sono. Insomma la programmazione generica non può essere soppiantata dalle macro.
La programmazione generica è la programmazione indipendente dai tipi di dato.
Java al posto dei template ha i Generics; Java nasce senza, però aveva l’ereditarietà, e la presenza di un tipo
che sta al di sopra degli altri tipi: object. Si usava quindi object per qualsiasi cosa ma poi ci si è accorti che
era un macello; quindi da lì hanno fatto i generics.
Quindi abbiamo template come nuova keyboard; come parentesi si usano < e > per il tipo, con class prima
del tipo.
Il template non si compila, perché il compilatore non ha nulla da chiamare; però si istanzia. Il tipo di ritorno
non viene considerato, perché alla fine il tipo di ritorno è un optional (la funzione chiamante può decidere
se usarlo come no), e qui non si usa.
Sì l’utente deve poter vedere il template. Il file .h contiene tutto, sia la dichiarazione che la definizione. Per
non avere il problema del cosice multiplo di template si mette la flag -fno-implicit-templates, oltre che
scriverlo nel codice (tipo template int min(int,int), cioè gli dici che template vai a generare codice).

Lezione 15 – 25/10
Nel template di classe esiste solo l’istanziazione esplicita, devo io fornire il parametro attuale
nell’istanziazione. Posso dare alcuni valori di default, ma come sempre una volta che li do tutti quelli che
stanno a destra devono essere pure loro di default. Cerr si usa per fare output su errore; quindi fa output
come cout ma serve per indicare che c’è stato un “errore” (si discrimina un uotput anomalo). Exit() provoca
l’immediata terminazione del programma, comunicando al processo che ha invocato il programma un
valore di stato (che decido tra () ).
In Queue QueueItem è oggetto diverso, e ovviamente esterno alla classe Queue; e si scrive, dato che è
template, QueueItem<T>.
Lo 0A che non vedo sarebbe quello che costruisce lo static field di C<double>, ma non viene utilizzato
quindi non compare (::s viene usato solo su <int>).

Lezione 16 – 26/10
Annidamento template associato: all’interno del template di classe vado a inserire un membro che è una
classe che dipende dal contenitore; questo è un tipo implicito, e il compilatore ha bisogno di un aiutino: si
usa una keyword per indicare al compilatore che usiamo un template che dipende dai parametri di tipo del
contenitore. Annidamento non associato: all’interno del template metto un template che ha ulteriori
parametri di tipo/valore, specifici suoi (e lui può usare i suoi parametri di tipo che quelli del contenitore).
La keyword è tipename. Per l’annidamento non associato si usa template. Comunque secondo Ranzato il
90% degli annidamenti sono associati.
Insomma bisogna ricordarsi typename.
Idea dell’albero con k figli come array di puntatori a nodo.
Tipicamente la modalità di distruggere i nodi in maniera profonda viene fatta con i puntatori smart, si evita
di ridefinire il distruttore all’interno della classe (perché è rischiosa, essendo profonda).
Con il minimo indispensabile di studio le funzioni della memoria profonda si fanno, dice Ranzato.

Lezione 17 – 27/10
Guardati esercizi-27-12-2021.cpp, o ancora meglio riguarda l’inizio della lezione.
Cppreference.com e cplusplus.com bei siti con informazioni varie.
Tempo ammortizzato: n inserimenti e o rimozioni ne faccio la media risulta che è sempre costante.
Lezione 18 – 29/10
Da sapere per il compito differenza tra iteratore costante e iteratore. Ovviamente è la solita storia di
quando trovi il const (valori che ci passi e tipo di ritorno).
--begin è undifined behavior; l’interpretazione ragionevole è quella di un iteratore non definito.
Deque è contenitore ad accesso casuale (deque = double end queue): supporta l’operatore di
indicizzazione, overloadiong di subscripting. Quindi sono definiti gli overloading di tutti quegli operatori che
vedi sulla slide.
Si può fare shrink di array ma Ranzato non ne vede l’utilità (perché non è che si distruggono le celle,
rimangono lì allocate).
Metodo da usare normalmente è la push_back (aggiunge un elemento in coda), e sappiamo che il suo costo
è costante (o di 1). Pop_back chiama il distruttore per l’ultimo elemento; avviene in tempo costante.
Ordine lessico-grafico: cerca bene la definizione.
Push_back la posso invocare per tutti i contenitori.
Se uso list si tratta di una lista doppiamente linkata (sizeof è size della lista + 2 per i puntatori, quindi + 16B);
rispetto al vettore che mi dà un elemento in tempo costante, la lista deve scorrersi tutta. Non c’è
overloading di subscripting sulla lista. Fare inserimenti su una lista costa sempre lo stesso tempo, però ho
bisogno di un iteratore che va a metà della lista.
Tipicamente le chiavi nei set vengono mantenute ordinate mediante un albero. Un set è un insieme
matematico di elementi senza ripetizioni, il multiset è un insieme di elementi ognuno dotato di una certa
molteplicità.
Funtore (UnaryFunction): un oggetto che svolge il ruolo di funzione. For_each mi permette di dargli un
intervallo di elementi su cui applico una funzione.

Lezione 19 – 02/11
Sul continuo dell’esercizio: c’è un costruttore di iterator, messo nella parte privata perché io ovviamente
non voglio che dall’esterno si facciano iteratori così. Nel costruttore c’è bool b = false, che serve a
controllare se c’è il past-the-end; inizializzato a false perché ci si aspetta che quello sia un caso limite.

Lezione 20 – 03/11
Verso l’inizio della lezione dice un esercizio che potrebbe mettere sul compito.
Per controllare se due iteratori sono uguali controllo se lo sono i puntatori. Non c’è bisogno di controllare i
due pte, perché se sono past-the-end puntano esattamente allo stesso indirizzo.
Esercizi-29-10-2012.cpp campione di esercizio importantissimo.

Inizio parte 2
Is-a meglio dire “è anche un”. Più importante tra le ereditarietà è l’ereditarietà pubblica.
Quindi scrivendo class dataora : public orario sto dicendo che eredito la parte pubblica del tipo orario.
Ora bisogna distinguere classe e tipo, perché con l’ereditarietà può succedere che una classe non sia un
tipo. Sottooggetto è oggetto padre. Subtyping in questo campo vuol dire estrarre il sottooggetto.
Estenzione: prendi oggetti e ne aggiungi, quindi “estendi” un tipo di dato.
Ridefinizione: cambia comportamento di funzioni; diverso, anche se sottilmente, a specializzazione, perché
questa ne “specializza” il comportamento, mentre ridefinizione lo ridefinisce completamente.
Tipo statico è dichiarazione decisa dal programmatore; dinamico è il tipo effettivo di una variabile
puntatore che cambia a runtime.

Lezione 21 – 05/11
Open world assumption – l’idea degli oggetti dinamici. Un tipo dinamico di un puntatore è un sottotipo del
tipo statico di quel puntatore.
Insomma privata inaccessibile a chiunque, nemmeno i sottotipi; parte protetta lascia accesso ai sottotipi.
Accoppiamento di tipi è una bad thing. Protetto si riferisce a tutti i passi di gerarchia.
Il discorso di sottoclasse ma non sottotipo si applica con l’ereditarietà privata. Perché derivazioni protette e
private non supportano il subtyping.
Rule of thumb per i campi dati: metti privato fino alla fine, se proprio non c’è nulla da fare metti protetto.
Ereditarietà che supportano il subtiping sono quelle pubbliche (e sono la maggior parte).
Quindi l’ereditarietà privata è sostanzialmente un dettaglio implementativo, dal punto di vista del design
non cambia nulla. La tabella con le freccette che fa vedere l’accessibilità in base all’ereditarietà si riferisce
all’esterno della classe: quindi, se faccio ereditarietà privata, all’esterno della classe la roba ereditata è vista
come privata.
Lo static cast serve sì per convertire tipi primitivi, ma il suo vero utilizzo è quello di convertire i tipi
puntatore all’interno delle derivate. Nell’esempio vengono convertiti un puntatore e un riferimento dalla
base alla derivata; il problema ovviamente è che ci sono degli attributi, nella derivata, non previsti dalla
base, quindi lì il compilatore darà un qualche tipo di errore.
Attenzione che “protetto” non vuol dire accessibile sempre e comunque all’interno della classe derivata:
vuol dire “accessibile mediante i sottooggetti” (esempio “sul significato di protected” con static void
stampa() ).

Lezione 22 – 09/11
Ovviamente per il compilatore non c’è differenza tra un metodo che inizia con operator e uno definito da
noi.
Name hiding rule: se nella classe derivata c’è una ridefinizione di un nome, tutte le versioni di quel nome
nella base non sono disponibili nella derivata; in questo senso sono nascoste, perché per invocarle devo
usare :: .
Binding (legame tra oggetto di invocazione e funzione) è statico, è determinato dal tipo statico (tipo di
dichiarazione) del puntatore di invocazione. Gli oggetti non sono polimorfi, solo i puntatori (e quindi
riferimenti).
Un oggetto della derivata è rappresentato da un sottoggetto della base e poi dai field propri della derivata.
Ovviamente se in costruttore e lista di inizializzazione non specifico niente vengono usati valori di default.
Se sono in una gerarchia di tipi, cioè mi trovo come sottoclasse a B e già B è definito per ereditarietà da A,
non mi occupo di A; quindi qualsiasi chiamata debba fare sarà sempre a B. Il costruttore di copia chiama
anche il costruttore di copia del sottoggetto.
La ridefinizione dell’operatore di assegnazione non ha lista di inizializzazione. Questo fa assegnazione per
sottoggetto e per dati propri, ognuno col proprio tipo. Proprio come ci aspettiamo.
Distruttore esegue corpo (se c’è), distrugge campi dati propri e poi sottoggetto.
Prima avviene la costruzione del sottooggetto e poi quella dell’oggetto.

Lezione 23 – 11/11
Mettendo le funzioni sotto protected si rendono disponibili alla classe in cui si trovano e alle sue
dipendenze.
Se funzione è virtual è passibile di binding dinamico. Overriding è l’overloading di una funzione virtuale.
Atttenzione quando faccio overriding di una funzione virtuale rimango virtuale (quindi scrivendolo non
serve mettere virtual).

Lezione 24 – 12/11
Keyword = 0 su una funzione vuol dire che è astratta.
Se una funzione virtuale ha valori di default, nel fare l’overriding non serve rimettere i valori di default,
perché li prende da prima (nell’esempio virtual void m(int x=0) poi mette m(int x) senza dover riscrivere
x=0). Se invece vado a fare overriding cambiando valore di default allora sto facendo una nuova funzione,
non un overriding (es di void m(), perché tento di ridefinire il caso con valore di default () ).
C’è la possbilità di bloccare il late binding usando l’operatore di scoping; quindi se voglio usare una funzione
nella forma originaria uso ::.
Esempio del window manager è un esempio di codice estensibile, perché tenendo tutto astratto alla shape
anche se in futuro poput/windows o quel che sia cambieranno comunque il codice andrà bene.
Tipicamente i distruttori vanno marcati virtuali. Anche se mi andasse bene il distruttore standard c’è la
necessita di marcarlo virtuale (e quindi riscriverlo aggiungendo virtual); nell’esempio ho virtual ~B e virtual
~C, che anche se non sembra è un overriding. Il distruttore se dichiarato virtuale nella base mantiene la
virtualità per tutta la gerarchia (come un po’ tutti i tipi…); la peculiarità del distruttore è quindi che il nome
può cambiare.
I tipi astratti servono per una classe/un metodo che non vengono istanziati, e che di per sé non servono; un
esempio è, nell’esempio del window manager, il draw di Shape. Shape di per sé non è nulla, quindi draw
non serve; e quindi è astratto. Nonostante una classe sia astratta va messo il costruttore, perché gli oggetti
della classe vivranno poi come sottoggetti.
Nell’esempio C rimane astratta perché aggiunge una funzione ma non conclude il contratto lasciato astratto
dalla classe base; invece D è concreta perché ne fa l’overriding.
Una classe è astratta se contiene almeno un metodo virtuale puro (astratto).
Un trick per rendere astratta una classe è, piuttosto che aggiungere metodi astratti inutili, rendere astratto
il distruttore; quindi si fa ~B = 0 e poi si dichiara fuori dalla classe.

Lezione 25 – 17/11
Esempio di classe lavoratore: il lavoratore di per sé non esiste, ma ci sono caratteristiche comuni a quelle
che saranno le sottoclassi di Lavoratore.
Classi astratte hanno costruttori che serviranno per le sottoclassi.
Ricordiamoci che se la classe è polimorfa, quindi è lì per essere estesa, tipicamente ha il distruttore virtuale.
Typeid ritorna un oggetto (blindato) chiamato typeinfo. Operator== mi serve quindi per vedere se una
determinata espressione ha un certo tipo. Typeid è blindato infatti perché costruttore e costruttore di copia
sono messi nella sua parte privata.
Maniera più semplice per rendere una classe polimorfa è mettere il distruttore virtuale.
In genere nei contesti si è interessati a tipo e sottotipo; perciò difficilmente typeid è usato, perché lui
guarda all’ugualianza tra tipi.
Downcasting (dynamic_cast): convertire un puntatore/riferimento da un tipo della classe base a un suo
sottotipo. La conversione ha successo se e solo se il tipo convertito TD(p) è compatibile con il tipo di
partenza (è un suo sottotipo?); faccio questo testo con dynamic_cast<D*>(p) != 0 (è compatibile con il tipo
target?). Il dynamic_cast con riferimenti nella pratica non si usano perché andrebbero accoppiati con il try e
catch (che sono il diavolo!); risolvo la situa mettendo davanti al riferimento il &, e così diventa puntatore.
Importantissimo: usare metodi virtuali piuttosto che fare type checking, il più possibile.
Il tipo target è quello tra <>, che quindi sarà un sottotipo di quello tra (). Se la conversione fallisce ritorna
nullptr.
Is-a == è un. Se Ranzato scrive “è un” pensa alla relazione is-a. Utilizzare typeid piuttosto che dynamic_cast
= -2 punti /10.
Il type-checking è brutto perché rende il codice non estensibile (ovviamente, mettendo dei check creo dei
branch, quindi non è estensibile).

Lezione 26 – 19/11
Una funzione virtuale è un contratto che vuole essere ridefinito, quindi che senso ha metterla privata?
Nessuno a pelle, e non per te, ma in teoria qualcosa può esserci.
Consiglio di riguardare la lezione per rivedere gli esercizi fatti.
Se la classe è polimorfa il distruttore è virtuale.
Costruttore di copia per una classe polimorfa non si può fare (anche perché verrebbe un codice assurdo
provandoci). Ciò che si può fare è definire un’”interfaccia”, come quella di una classe che clona (clonable
nell’esempio).
Clone ritorna un puntatore clonabile, const o no in base a marcatura sul clonable di ritorno. Ovviamente la
base sarà dotata anche di distruttore polimorfo. Quindi ogni classe che vorrà essere clonabile dovrà
implementare questo metodo (implementato dalla base).
Esercizio iZod: notare che nella base FileAudio non è specificato cosa vuol dire che un file audio sia di
qualità; questo vuol dire che è davvero mega astratta e che ogni derivata avrà una sua definizione di
qualità.

Lezione 27 – 23/11
Consiglio di riguardare la registrazione della lezione (fine degli esercizi).
Non essere wav vuol dire non essere wav e non essere nemmeno un suo sottotipo.

Pt.2 Ereditarietà multipla


Derivazione virtuale non c’entra niente con le funzioni virtuali. Si aggiunge virtual prima di public; in questo
modo ho un puntatore alla base virtuale, così da non avere ambiguità e doppie coppie. C e B non hanno più
la base A, ma hanno il puntatore ad A; a decidere il puntatore è D (classe che chiude il diamante).

Lezione 28 – 24/11
Base virtuale: è un supertipo ed è virtuale. Prima vengono costruite le base virtuali e poi il resto, sempre
con l’idea left-right-top-down. I sottooggetti delle basi virtuali non vengono ricostruiti perché sono in copia
unica.
Esempio del missile: la base solidoConCerchio è una base virtuale astratta.

Potrebbero piacerti anche