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
`B`
- private: tutti gli attributi che `B` eredita da `A` risulteranno `private` in `B`
Ereditare con `public` fa sì che la relazione tra derivata e base sia una IS-A, per
cui ad esempio posso usare un puntatore del tipo della classe base per riferirmi
a un'istanza della derivata.
Tuttavia questo potrebbe causare dei problemi se andiamo nella classe figlia
andiamo a fare l'overriding di alcuni metodi. Per risolvere il problema e forzare
il late binding (quindi la ricerca di quale metodo usare a runtime, cfr. nell'early
binding questo è risolto a compile time), possiamo anteporre la keyword
`virtual` ai metodi che ci interessano. Possiamo anche definire un metodo come
puramente virtuale in questo modo:
La presenza di un metodo puramente virtuale renderà la classe puramente
virtuale a sua volta, vale a dire non instanziabile e utilizzabile solo per derivare
delle classi figlie.
• Spiega in che modo sono diverse l'ereditarietà di tipo protected e l'ereditarietà di
tipo private, fornendo esempi di codice e implementazione.
Abbiamo tre tipi di ereditarietà in C++; nello specifico, quelli richiesti sono:
protected: tutti gli attributi protected e public della classe base verranno
ereditati come protected nella figlia;
private: tutti gli attributi non privati della classe base verranno ereditati come
private nella classe figli
• Spiega cosa si intende con ereditarietà multipla e come viene realizzata in c+;
spiega inoltre cos'è il diamond problem e come viene approcciato in dettaglio,
con esempi.
L’ereditarietà multipla è un meccanismo che permette a una classe di ereditare
da più classi base differenti. Consideriamo una classe A che eredita in modo
public da due classi B e C (in ordine).
Il diamond problem si verifica quando le due classi padri B e C ereditano esse
stesse da una stessa classe D. Se D contiene un attributo protected o public,
questo verrà ereditato sia da B che da C; come faremo a decidere quale
versione di tale attributo deve essere ereditata da A? La risposta è semplice: si
deve fare in modo che B e C ereditino da D in modo virtuale. Inoltre,
l’ereditarietà virtuale risolve anche il problema di chiamate multiple al
costruttore della classe D.
• Spiegare nel dettaglio cosa si intende per metodo astratto e fare degli esempi di
implementazione.
Un metodo si definisce astratto (o per dirla in maniera più propria per C++,
virtuale) quando ne viene
forzato il late binding
anteponendo alla firma la
keyword "virtual". Si forza
il late binding nelle
situazioni in cui facciamo
uso di ereditarietà e si corre
il rischio che istanziando
una classe a runtime
mediante la dichiarazione
di un puntatore alla classe
padre si richiami un
metodo pensando di
invocare quello della classe
figlia (classe effettiva a
runtime) ma si vada in
effetti a usare quello della
classe padre (classe statica
riconosciuta a compile
time); per l'appunto
risolvendo il binding del
metodo a runtime si risolve
questo problema.
Inoltre, mediante una
precisa sintassi la keyword
"virtual" può essere usata
per creare un metodo
puramente virtuale, ossia
privo di implementazione.
La presenza di un solo
metodo puramente virtuale è
sufficiente a rendere la sua
classe puramente virtuale a
sua volta e pertanto non
istanziabile; può solo essere
usata come classe padre per
altre derivate che dovranno
necessariamente fornire
un’implementazione per
tutti i metodi puramente
virtuali del padre per essere
istanziabili.
• Spiega approfonditamente quali sono le funzioni della keyword virtual, dove
possibile produci anche dei semplici esempi significativi.
La keyword virtual ha principalmente 2 significati, in C++. Il primo è di
forzare il late binding dei metodi che la vedono anteposta nella dichiarazione;
per late binding intendiamo che, quando abbiamo un metodo di una classe base
che viene overridato in una classe figlia e nel main assegniamo ad un puntatore
alla classe padre un'istanza allocata dinamicamente della classe figlia, il
binding del metodo non viene risolto a compile time, bensi a runtime.
Inoltre, con una precisa sintassi, virtual va a indicare che un certo metodo è
puramente virtuale, ossia che è una firma senza implementazione. La presenza
di un metodo puramente virtuale rende la classe stessa puramente virtuale, vale
a dire non istanziabile e utilizzabile esclusivamente come classe base da cui
derivare altre classi.
In ultimo, virtual è utilizzata nell'ereditarietà virtuale, un meccanismo che si
rende necessario nel momento in cui andiamo a trattare un caso di ereditarietà
multipla. Ereditare virtualmente, come nell'esempio qui sotto, previene la
doppia chiamata del costruttore della classe base da cui ereditano i due padri
della nostra classe figlia. (previene il diamond problem)
• Move semantics: descrivi i concetti di lvalue e rvalue e lvalue reference e rvalue
reference; per tutti questi produci esempi d'uso, ma soffermati in particolare
sugli ultimi e approfondisci ulteriormente le motivazioni che soggiacciono alla
loro introduzione.
La distinzione tra lvalue e rvalue a primo acchito può essere semplificata con
questa spiegazione: un lvalue si trova spesso a sinistra dell'uguale mentre un
rvalue si trova a destra. Ma cosa significa veramente questo?
Lvalue sono tutti quei valori che hanno uno spazio dedicato nella memoria
dello stack del programma e che quindi possono essere dereferenziati.
Rvalue sono tutti quei valori che sono temporanei (nella riga a=2; il 2 è un
intero temporaneo che non ha un indirizzo di memoria nello stack ma si trova
nello spazio di indirizzamento delle variabili temporanee, che a quanto pare è
un mondo a sé); i temporanei non sono dereferenziabili. O almeno così
fino al C+11: proprio in questa edizione del linguaggio è stata introdotta la
possibilità di ottenere una referenza a rvalue utilizzando l'operatore &&.
Questo rende possibile appunto ottenere una referenza a valore temporaneo che
serve per poter implementare quella che viene chiamata move semantics,
ovvero un meccanismo per rendere efficiente la creazione di variabili usando
invece che un costruttore di copia un costruttore move.
Mentre con il costruttore di copia quando noi istanziamo una certa variabile di
classe A
a1 prenderà il valore di A (12) facendo una copia dell'istanza temporanea che si
crea a destra dell'uguale, con il costruttore di move
L'istanza temporanea non viene copiata ma viene a tutti gli effetti "spostata" in
a2, così da evitare il peso di una copia.
Naturalmente questo non ha senso se la classe A contiene solo un intero, ma
inizia ad acquistare senso se A contiene matrici o oggetti molto pesanti allocati
nell'heap: risparmiare la copia di questi è senza dubbio una buona idea.
Il costruttore move viene quindi implementato in modo da rubare tutti i
puntatori dell'istanza temporanea, che tanto è temporanea e non serve a
nessuno, e darli all'istanza di cui ci importa qualcosa effettivamente, sotto un
esempio:
A questo punto i più curiosi tra di noi si chiederanno perché annullare il
puntatore del temporaneo? La risposta è presto detta: non sai mai che fine farà
il temporaneo, quindi se vuoi evitare che il caro n venga distrutto dal garbage
collector o cose simili devi fare in modo che il suo destino sia separato da
quello di _a .n.
• Parla del concetto di rvalue reference: cos'è, quando è stato introdotto e perché,
facendo esplicito riferimento a come si inserisce nell'ambito della move
semantics, con esempi di codice.
In C++ abbiamo i concetti di lvalue e rvalue: i primi possiedono un indirizzo in
memoria e si trovano sia a sinistra che a destra di un operatore di assegnazione,
i secondi sono dei valori che non possiedono indirizzo in memoria (solitamente
costanti letterali o valori temporanei) e si trovano solo a destra dell’uguale.
Esiste anche il concetto di lvalue reference, ossia un riferimento a un lvalue.
Se si prova ad utilizzare un rvalue come argomento di una funzione che accetta
lvalue reference, si ottiene
un errore a meno che
questo non sia passato
con const.
In C++11 è stato
introdotto il concetto di
rvalue reference, ossia un
riferimento a un rvalue.
Un rvalue reference è
un'entità dereferenziabile
ed è quindi tecnicamente
un lvalue.
Nell’ambito della move semantics, gli rvalue reference sono utilizzati dai
cosiddetti move constructors per creare nuove istanze di determinate classi a
partire da degli oggetti temporanei (che sarebbero quindi rvalue e non
potrebbero essere utilizzati in un costruttore normalmente): utilizzando la
funzione std::move(), infatti, possiamo ottenere un rvalue reference dell’istanza
passata e utilizzarla nel move constructor.
• Esiste un modo per passare un rvalue per riferimento che non preveda l'utilizzo
della &&? Mentre pensi a questa risposta racconta cose un rvalue e perché è
differente da un Ivalue.
Un lvalue è un oggetto che ha un preciso indirizzo in memoria ed è
referenziabile; un rvalue è un oggetto temporaneo che non possiede un
indirizzo di memoria e non è referenziabile, solitamente delle costanti letterali,
ad esempio (come numeri, `true`, `null`). Sono detti così perché gli lvalue
possono stare sia a sinistra che a destra di `=`, anche se solitamente li si trova
più spesso a sinistra; di contro, gli rvalue possono trovarsi solamente a destra
dell'uguale.
Per tutti questi motivi, se abbiamo una funzione che accetta dei riferimenti, non
sarà possibile passarle un rvalue.
Esiste un modo per far funzionare tutto anche senza rvalue reference: possiamo
dichiarare il parametro come `const` nella firma della funzione.
• Spiega la differenza tra copy constructor e move constructor e fai degli esempi di
implementazione e uso di copia profonda.
Un copy constructor è un costruttore usato per effettuare la copia profonda di
un'istanza di una certa classe a partire da un lvalue reference passato. Per copia
profonda intendiamo un meccanismo di copia in cui sia a creare una nuova
istanza di una certa classe del tutto indipendente da quella passata per effettuare
la copia; questo vuol dire che se la classe presenta degli attributi di tipo
puntatore la copia profonda non copierà semplicemente il valore del puntatore
(facendo sì che sia l'istanza passata che quella nuova puntino allo stesso
oggetto con grandi pr