Anteprima
Vedrai una selezione di 7 pagine su 26
Domande aperte Programmazione avanzata Pag. 1 Domande aperte Programmazione avanzata Pag. 2
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Domande aperte Programmazione avanzata Pag. 6
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Domande aperte Programmazione avanzata Pag. 11
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Domande aperte Programmazione avanzata Pag. 16
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Domande aperte Programmazione avanzata Pag. 21
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Domande aperte Programmazione avanzata Pag. 26
1 su 26
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

`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

Dettagli
Publisher
A.A. 2023-2024
26 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher edoardo1604 di informazioni apprese con la frequenza delle lezioni di Programmazione avanzata e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Trento o del prof Blanzieri Enrico.