Salta al contenuto
Note per Studenti Esercizio - Quiz su contenitori STL, override, sizeof, void e virtual table (secondo appello 2021-22, quiz)

Esercizio - Quiz su contenitori STL, override, sizeof, void e virtual table (secondo appello 2021-22, quiz)

Esame

Quiz del secondo appello del corso di Laboratorio di Programmazione, Ing. Informatica UniPD, a.a. 2021-22 (11 febbraio 2022, 90 minuti): dieci domande a quattro affermazioni vero/falso (giusta +1+1, sbagliata −0.25-0.25, non data 00). Gli altri esercizi dell'appello: Esercizio - Chiamata a funzione e layout della memoria (secondo appello 2021-22, teoria), Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria), Esercizio - Confronto tra map e vector (secondo appello 2021-22, teoria), Esercizio - Distruttore, copia e spostamento della classe Elements (secondo appello 2021-22, programmazione).

1) Un contenitore STL:

affermazione V/F motivo
È una sequenza di elementi ( begin():end() ] F l'intervallo giusto è semiaperto a destra: [begin,end)[begin, end)
È una sequenza di elementi ( begin():end() ) F anche qui il primo estremo è escluso: sbagliato, begin() appartiene alla sequenza
È implementato in modo che il costruttore di copia e di spostamento siano disabilitati F un contenitore supporta la copia (assegnamento e costruttore di copia)
Possiede tipi iteratore chiamati iterator e const_iterator V vector<int>::iterator, list<string>::const_iterator. Vedi STL - sequenze, iteratori e container (vector, list, string)La STL (Standard Template Library) fornisce contenitori (vector, list, map, ...) e algoritmi generici (sort, find, accumulate, ...) tenuti insieme da una interfaccia unificata: la sequenza, un intervallo [begin, end) di elementi gestito tramite iteratori (oggetti che identificano un elemento: * dereferenzia, ++ avanza, == e != confrontano; end è il primo elemento dopo l'ultimo, quindi una sequenza vuota ha begin == end). Gli algoritmi conoscono solo gli iteratori e i contenitori espongono solo iteratori, così i due mondi sono disaccoppiati e cambiare contenitore non cambia l'algoritmo. Un iteratore di lista è una classe annidata con un puntatore al nodo; auto deduce il tipo dell'iteratore. vector (contiguo, [] e at(), insert/erase costosi e che invalidano gli iteratori), string (un vector di char con + e +=), list (nodi concatenati, insert/erase senza spostare gli altri elementi, niente []) e char[] (array C: dimensione fissa, niente iteratori, == confronta i puntatori) a confronto.STL - sequenze, iteratori e container (vector, list, string) →

2) L'override di una funzione in una gerarchia di classi in C++:

affermazione V/F motivo
Permette di cambiare il numero o il tipo di argomenti della funzione nelle classi derivate F con parametri diversi non è override ma una funzione nuova che nasconde quella della base
Permette di cambiare solo la constness della funzione nelle classi derivate F nell'override stesso nome, stessi tipi e stessa constness: f() const e f() sono funzioni diverse (nelle slide DD::f() non è un override di B::f() const)
Deve per forza essere esplicitata dalla keyword override nelle classi derivate F override è facoltativa: serve solo a far verificare l'intenzione al compilatore
Deve per forza essere esplicitata dalla keyword virtual nella classe base V la funzione della base deve essere virtual (anche ereditata come tale), altrimenti si ha solo nascondimento. Vedi C++ - ereditarietà, funzioni virtuali e layout degli oggettiL'ereditarietà pubblica (class D : public B) esprime la relazione "is a": D eredita da B i membri public e protected e li estende; la composizione esprime invece "has a". Una classe base di una gerarchia disabilita copia e assegnamento (=delete) perché copiando una derivata in una base si ha lo slicing. Una funzione virtuale può essere ridefinita nelle derivate (override: stesso nome, tipi e constness) e la funzione chiamata si decide a run-time in base al tipo dell'oggetto, non del puntatore o del riferimento; override esplicito con la keyword override; una funzione virtuale pura (= 0) obbliga le derivate a ridefinirla e rende astratta la classe; il distruttore di una classe base polimorfa è virtuale. Il meccanismo è realizzato con una vtbl per classe (tabella di puntatori alle funzioni) e un vptr in ogni oggetto; i dati della derivata seguono quelli della base.C++ - ereditarietà, funzioni virtuali e layout degli oggetti →

3) Quando manda in esecuzione un programma, il sistema operativo riserva spazio per:

affermazione V/F motivo
Codice eseguibile/codice macchina V text segment
Free store V lo heap: la regione per new/delete, che parte piccola e cresce
Stack V i record di attivazione
Variabili globali V initialized data e BSS

Il layout del processo ha cinque regioni, tutte riservate dal sistema operativo (C++ - chiamate a funzione e layout della memoriauna chiamata a funzione crea un record di attivazione (RA) con una copia dei parametri e le variabili locali, impilato nello stack (LIFO) e tolto all'uscita; la dimensione dipende dalle variabili locali e il tempo per inserirlo è costante; il processo ha cinque regioni di memoria: text (istruzioni, spesso read-only), initialized data (globali e statiche inizializzate), uninitialized data o BSS (globali e statiche non inizializzate, azzerate dal sistema), stack (RA) e heap (allocazione dinamica con new e delete); la durata (storage duration) di una variabile è diversa dallo scope: le globali e le static sono statiche (esistono per tutto il programma, inizializzate una volta), le locali sono automatiche (create all'entrata nel blocco, distrutte in ordine inverso all'uscita); le globali sono inizializzate prima del main ma l'ordine fra file diversi non è definito.C++ - chiamate a funzione e layout della memoria →).

4) L'operatore sizeof():

affermazione V/F motivo
Se applicato a un contenitore STL restituisce il numero di elementi contenuti F dà la dimensione in byte dell'oggetto contenitore (puntatori e contatori), non degli elementi: per quelli size()
Funziona con un nome di tipo o di variabile/espressione V sizeof(int), sizeof(x), sizeof x
Il valore ritornato può dipendere dall'architettura utilizzata V un puntatore è 4 o 8 byte, long 4 o 8
Ritorna la dimensione in bit dell'argomento F in byte

5) Il puntatore void*:

affermazione V/F motivo
È un puntatore a memoria raw V indirizzo senza tipo
È possibile assegnare qualsiasi puntatore a un void* V conversione implicita sempre ammessa
Se utilizzato in una funzione template, il tipo viene dedotto automaticamente F void* ha già perso il tipo: il template deduce void*, non il tipo originale; per riaverlo serve static_cast
Non si può dereferenziare V *pv e pv[2] non compilano. Vedi C++ - puntatori, array e castun puntatore è una variabile che contiene un indirizzo di memoria e sa il tipo dell'oggetto puntato (T*); si ottiene l'indirizzo con & e si accede al dato con * (dereference, un lvalue), nullptr indica nessun oggetto e due puntatori a tipi diversi sono tipi diversi; void* è un puntatore a memoria grezza (accetta qualsiasi puntatore ma non si dereferenzia) e i cast (static_cast, reinterpret_cast, const_cast) servono per interfacciarsi con hardware e codice non modificabile; il puntatore, a differenza della reference, si può spostare, può essere nullptr e va dereferenziato, e assegnarlo copia l'indirizzo (shallow copy) mentre assegnare una reference copia l'oggetto; un array C è una sequenza contigua di elementi dello stesso tipo, con dimensione costante, senza controllo dei limiti, che passato a una funzione decade in un puntatore al primo elemento; l'aritmetica dei puntatori somma o sottrae interi spostando di n elementi (non di n byte) e la differenza fra puntatori è in elementi; gli errori classici sono nullptr, puntatori non inizializzati, accessi fuori dai limiti e puntatori a oggetti usciti dallo scope.C++ - puntatori, array e cast →

6) L'istruzione int age = 42;:

affermazione V/F motivo
Contiene un'inizializzazione V = 42 dà il valore alla nascita
È una definizione di variabile V dichiara e riserva memoria
Può riservare spazio nel free store F un int di questo tipo sta nello stack (locale) o nei dati (globale); il free store è solo per new
Può riservare spazio nell'initialized data segment V se è una variabile globale o static (inizializzata a un valore diverso da 0)

7) La classe DataBuffer (char* data_buffer allocato con new char[kLength]; std::vector<int> data_vector; std::shared_ptr<double> data_smart_buffer costruito con new double[kLength]; il distruttore fa solo delete[] data_buffer;), con l'assegnamento di copia generato dal compilatore:

affermazione V/F motivo
L'assegnamento di copia effettua una shallow copy corretta di data_smart_buffer V la copia di uno shared_ptr condivide il buffer e aumenta il contatore: è la semantica voluta
L'assegnamento di copia effettua una shallow copy corretta di data_buffer F copia il puntatore: shallow, ma non corretta (due delete[] sullo stesso buffer, e il buffer vecchio va perso: leak)
L'assegnamento di copia effettua una deep copy corretta di data_vector V la copia di vector duplica gli elementi
Il distruttore è incompleto: serve chiamare reset dello smart pointer per non avere memory leak F lo smart pointer libera da solo alla distruzione

(Un'osservazione sul materiale: shared_ptr<double> inizializzato con new double[kLength] userebbe delete e non delete[]: è un errore latente della classe d'esame; dal C++17 si scrive shared_ptr<double[]>. Non incide sulle risposte.)

8) Gerarchia con classe base B e derivata D, virtual table (VT):

affermazione V/F motivo
È creata una virtual table per ogni oggetto istanziato F una per classe; ogni oggetto ha un vptr che la raggiunge
Una volta create, le VT sono modificabili dal programmatore per migliorare l'efficienza F le costruisce il compilatore
Contengono un riferimento a ciascun record di attivazione presente nello stack F contengono indirizzi di funzioni virtuali
Una funzione membro chiamata tramite un B* che punta a un oggetto di classe D utilizza la VT di D V il vptr dell'oggetto punta alla VT della sua classe vera

9) La classe MyRecords (costruttore senza argomenti, costruttore MyRecords(int i) con records.reserve(i), membro std::vector<std::string> records):

affermazione V/F motivo
Il codice è incompleto: deve essere definito un distruttore per pulire records prima che l'oggetto sia distrutto F vector ha il suo distruttore, chiamato da quello generato
L'istruzione MyRecords m = 5; compila V il costruttore a un argomento non è explicit: conversione implicita
È dotata di un costruttore di copia definito dal compilatore il cui funzionamento è corretto V copia il vector membro, che è profonda
È dotata di un costruttore di spostamento definito dal compilatore il cui funzionamento è corretto V non ci sono costruttori di copia né distruttore dichiarati: si genera, e sposta il vector

10) Un errore di linking:

affermazione V/F motivo
Può essere causato da una funzione dichiarata in un file e definita in un altro file compilato assieme a quello dove è utilizzata F è l'organizzazione normale a più file: nessun errore
Può essere causato da una funzione dichiarata e utilizzata, ma non definita V undefined reference
Può essere causato da un'istruzione con un errore di sintassi F errore del compilatore, il linker non parte
Può essere causato dalla mancanza di un #include di un header F senza dichiarazione l'errore è di compilazione (nome non dichiarato). Vedi C++ - introduzione, compilazione e linkingC++ è un linguaggio compilato e standard che dà controllo totale sulla macchina (basso livello) e strumenti di astrazione (alto livello); un programma parte da main, usa gli stream (std::cout << ...) e le direttive del preprocessore (#include); il passaggio da sorgente a eseguibile ha tre fasi, preprocessore (espande #include e macro), compilatore (controlla sintassi e tipi e produce un file oggetto in codice macchina) e linker (unisce i file oggetto e le librerie risolvendo i nomi); gli errori di sintassi e di tipo sono del compilatore, i simboli dichiarati ma non definiti (undefined reference) sono del linker; si compila da riga di comando con g++ -o eseguibile sorgente.cpp -lLibreria.C++ - introduzione, compilazione e linking →

Versione ripasso

Teoria collegata