Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
SVOLGIMENTO:
Facciamo prima il diagramma delle classi con ereditarietà:
ZooAnimal <-v-- Beer <--- Panda (schema a diamante)
<-v-- Reccon <---
<--- Endangered <---
ZooAnimal è il primo ad essere costruito.
1) Stampa:
- costuisco ZooAnimal
- costruisco Endengered
- costruisco Beer
- costruisco Reccon
- costruisco Panda
INFO VARIE:
Doxygen genera anche un grafico che indica la gerarchia di classi.
La relazione IS-A permette la conversione in automatico.
- Una ereditarietà pubblica con soli metodi static è fatta per pigrizia cioè è fatta per ereditare senza
doverle riscrivere (mezzo abuso).
qt class diagramm: ogni rettangolo è una classe (la realtà è molto complicata).
http://doc.qt.digia.com/extras/qt43-class-chart.pdf
In questo caso vanno un po' in crisi i principi che abbiamo fatto; qui l'utente può mettersi in lato
astratto o concreto DIPENDE ma in genere in questo caso, serve maggiormente la concretizzazione;
Es: devo fare un programma che chiede agli oggetti come sono tramite dynamic_cast (cose
"negative" in certi contesti ma non in questo).
Gerarchie così complicate sono rivolte a problemi complessi ma se a me interessa solo la parte
concreta, allora non è molto complicato usarle.
Due gerarchie di questa complessità, non sono utilizzabili entrambe contemporaneamente perché
sono fatte per legarvi ad esse (questione di marketing).
PPL - Classe poliedro: si fa subito una suddivisione tra quelli chiusi e quelli che possono essere sia
chiusi che aperti (sui primi, le operazioni sono più veloci); entrambe suddivisioni che fanno parte di
una classe contenente un flag che indica com'è il poliedro.
Qui le proprietà SOLID non valgono ma dato che una modifica difficilmente ci sarà, allora si hanno
degli if-else sparsi nel codice.
Perché usare IS-A e non il contenimento? Potevano farlo ma erano pigri; il poliedro è pesante e
scrivere tutte le funzioni "passa-carte" è noioso.
Dato che la classe base non può essere utilizzata, come facciamo ad obbligare l'utente ad usare una
delle due suddivisioni? Facciamo i costruttori della classe base come protected (utilizzabili solo
dalle derivate ma non dall'esterno).
Design patters: schemi di progettazione generali che indicano problemi che capitano spesso nel
voler creare un progetto complicato.
Frequenti sono (wikipedia):
- I pattern creazionali: nella progettazione con polimorfismo dinamico devi utilizzare astrazioni
ma esiste un punto (tanti punti) nei quali bisogna generare nuovi animali e non si possono costruire
animali astratti quindi si devono anche creare animali concreti.
Questo implica che vi è una dipendenza dall'implementazione.
I partern creazionali gestiscono queste dipendenze mettendole in un unico file "object factory" che
contiene gli oggetti concreti (nel caso della fattoria era maker.cc il quale doveva crearli).
Così è più facile gestire le dipendenze.
Questo però significa che serve anche una factory astratta.
Se devo costruire un pulsante potrei avere due librerie diverse che costruiscono pulsanti in modo
diverso e mi serve quindi una classe astratta per la gestione.
Di oggetto che costruisce il pulsante comunque ne devo avere solo uno e quindi viene gestito anche
questo.
- adapter: adattano tra di loro due interfacce diverse.
- decorator: hai un oggetto pizza che ha anche un costo e gli ingredienti ma questi oggetti vengono
fuori in molti modi diversi; non possiamo immaginarci tutte le possibili combinazioni quindi
consentiamo all'utente durante l'esecuzione di gestire questa cosa consentendo di comporre a
piacere tutte le pizze.
Il decorator è un oggetto che eredita dalla pizza e fornisce un'interfaccia uguale a quella della pizza;
al suo interno si memorizza un riferimento a puntatore che punta ad un'altra pizza; siccome il
metodo che calcola la pizza è overriding, il decorator sovrascrive il metodo per calcolare l'altra
pizza.
Il decorator è una pizza che però si memorizza (sovrascrive) con un'altra pizza cambiando quindi
anche i metodi ad essa legata.
Il decoretor è una pizza (non concreta) nella quale se si aggiunge la mozzarella, io aumento il
prezzo di un certo valore.
Al PC: voglio la finestra con un certo bordo allora creo un decorator che si applica quando creo la
finestra (sono pattern).
- state: registra determinati stati che possono essere utilizzati per cambiare qualcosa.
Uccidi 100 mostri, ricevi una medaglia (la medaglia è un oggetto astratto che dipende dallo stato).
Il numero di ciò che cambia rispetto allo stato, non è fissato.
L'oggetto osservato ha 3 metodi (e una lista):
2 uno per aggiungere e l’altro per togliere alla lista puntatori ad oggetti che osservano lo stato.
1 quando cambia lo stato, l'osservato invoca un metodo (notified) che notifica il proprio
cambiamento agli oggetti della lista. Gli osservanti decideranno poi loro cosa fare.
- visitor: quando la gerarchia è complicata (come quella per gestire le espressioni) dove metto i
virtual?
Se per caso l'utente vuole far fare operazioni diverse e nuove?
Il visitor va ad effettuare controlli (visita) su ad esempio gli animali quindi su gallina, maiale etc
Lato negativo:
- (IL SERVER) Se la nostra astrazione non va più bene quindi se serve una modifica nei metodi,
questo comporta che le classi concrete inventate finora dovranno essere modificate.
- Il visitor va in crisi quando qualcuno aggiunge una nuova classe concreta alla mia gerarchia, il
visitor non sapranno analizzare la nuova classe concreta (in questo caso bisogna quindi aggiornare i
visitor per renderli compatibili con le nuove classi concrete).
FINE info varie.
Costruttori virtuali
Non posso dichiarare virtuali i costruttori perché ho bisogno del this ma il virtual non ha this.
In giro però si trovano costruttori virtuali che però non sono costruttori ma si comportano come tali.
Se io volessi creare una copia concreta di animale che è astratto, come faccio?
Con la costruzione virtuale:
class Anmale {
public:
virtual Animale* clone() const = 0;
};
class Cane : public Animale {
public:
Cane* clone() const { //Se cambia qualcosa nel nome, non vale l'overriding ma qui restituisco un
puntatore Cane e non puntatore ad Animale; questo è possibile se esiste la relazione IS-A (LSP) tra
animale e cane con la quale avviene quindi overriding
return new Cane(*this);
}
}
int main() {
Animale a;
}
- Questo permette di poter costruire un oggetto come prototipo di un altro oggetto (che è il *this).
Es: voglio costruire un sanbernardo:
Costruisco un Animale → Quado costruisco il cane (nasce), voglio che faccia un verso (virtual)... fa
quello del Cane o del Sanbernardo (classe concreta di Cane)?
E’ meglio evitare mettere metodi virtual nei costruttori (o distruttori).
Mentre è proprio sbagliato usare virtual dentro a costruttori (o distruttori) che accedono a dati
dell'oggetto.
Polimorfismo
Esistono 2 forme di polimorfismo:
1) polimorfismo statico (es template):
- Scriviamo degli schemi di funzione con cui generare nuove funzioni.
- Molto più efficienti perché a compilazione sappiamo già chi invocare e possiamo anche
ottimizzare.
- Viene generato molto codice e in certi casi (rari) si peggiorano le prestazioni perché si ha
parecchio codice da caricare in più.
Per risolvere in parte questo problema in certe implementazioni della stl, ci sono operazioni che
valgono per più tipi (vector, liste etc) tramite l’utilizzo di puntatori a qualcosa che sono solo loro a
cambiare (questo soluzione vale anche per il dinamico).
2) polimorfismo dinamico:
- Utilizza classi dinamiche con funzioni astratte perché si decide a tempo di esecuzione cosa farà la
funzione.
- Si fanno più controlli a tempo di esecuzione.
- Si genera meno codice.
Si sta utilizzando maggiormente lo statico (anche perché è meglio intervenire durante la
compilazione).
Es: il fattoriale è meglio farlo durante la compilazione SE si hanno già i dati del fattoriale così si
inserisce il numero ed è più veloce.
Nel linguaggio C++ ci sono molte costanti da scrivere e spesso sono costanti derivate (calcolate) da
altre; nel 2003 non si poteva definire costanti usando altre costanti salvo in casi particolari.
Hanno inventato i const expr: se viene invocata una funzione con dati costanti, si calcolala a tempo
di compilazione e non durante il runtime.
Asserzioni: controlla (solo) durante il debugging e funziona solo a tempo di esecuzione.
Si nota che però certe asserzioni si possono controllare durante la compilazione e quindi si sono
create le static assert:
- se l'asserzione è violata, il programma non compila.
- non c'è distinzione tra compilazione e debugging; tutto il controllo avviene durante la
compilazione.
Si sta quindi creando una separazione ma che comunque non è completa:
Linguaggi molto dinamici: scripting, java, C++ (vecchio)
Linguaggi molto statici: C++
Es: class C {
template <...>
virtual void foo() { … }
}; // NON si può fare in C++
// Il template indica tante possibili funzioni che non possono essere tutte trattate virtualmente
template <...>
class C {
virtual void foo();
} //SI PUÒ fare
// Fissati i valori del template, esiste un solo metodo possibile da trattare come virtual
Multy-Threading e Multy-processing
Supporto alla concorrenza (nuovo, che nel C++ 2003 non esiste)
- Abbiamo sempre ragionato su un singolo flusso di istruzioni (un solo thread):
Program counter (esegue istruzioni), heap (memoria), memoria statica, stack, lista istruzioni etc
- Le macchine moderne hanno più circuiti con tante CPU che possono eseguire più cose
contemporaneamente. Come si può sfruttare questo eccesso di hardware?
1) Multy-processing: ci sono in esecuzione più processi (istanza di un programma in esecuzione; se
ne apro 50, ne avrò 50 uguali che agiscono senza interferire se non lo decido io).
Ogni processo è appunto separato, ma serve un modo per farli interagire.
Per interagire, hanno 2 modalità:
- 1: comunicare attraverso effetti esterni (un processo scrive su un file e un altro processo legge
sullo stesso file).
- 2: chiedere al sistema di avere un blocco di memoria condiviso.
Il fatto che siano indipendenti però è una cosa buona perché si evitano problemi (possono essere
distribuiti più facilmente).
Per farli interagire invece si fatica e si rallenta (più lenti rispetto la situazione ideale).
2) Multy-threading: ho tante