Esercizio - Copiare da array a array dinamico senza delete (terzo appello 2022-23, programmazione)
Prova di programmazione (60 minuti) del terzo appello 2022-23 (30 giugno 2023) del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: C++ - smart pointerGli smart pointer (classi template di std::array), C++ - eccezioni e gestione delle risorse (RAII)Un errore che non si può gestire dove avviene si segnala con throw e si gestisce con try/catch più in alto; la libreria standard ha la gerarchia std::exception (logic_error e runtime_error con le loro derivate) e le eccezioni proprie si derivano pubblicamente da una di esse; si cattura per riferimento. Le eccezioni mettono in crisi il codice che acquisisce risorse (memoria con new, file, lock, socket): se una chiamata lancia prima della delete, la risorsa si perde. La soluzione è RAII (Resource Acquisition Is Initialization): acquisire la risorsa nel costruttore di un oggetto locale e rilasciarla nel distruttore, perché all'uscita da uno scope, anche per eccezione (stack unwinding), i distruttori degli oggetti locali vengono chiamati. Garanzie di una funzione: basic (nessuna perdita, stato valido), strong (commit or rollback: se fallisce lo stato osservabile è quello di prima) e no-throw (non lancia).C++ - eccezioni e gestione delle risorse (RAII) →. È la variante di Esercizio - Spostare i valori di una list in un array dinamico senza delete (quarto appello 2021-22, programmazione): lì i valori si spostano da una lista, qui si copiano da un array.
Testo. Progettare un programma che: 1. crei uno
std::arraydi valori reali e lo popoli usando 20 valori a piacere; 2. allochi dinamicamente un array stile C di dimensione 20; 3. scorra lostd::arraye copi nell'array stile C i valori superiori a una soglia costante definita opportunamente; 4. esca senza usare esplicitamentedelete/delete[]nel codice dell'esercizio e senza causare memory leak.
Ragionamento
std::array<double, 20>: contenitore di dimensione fissa (parametro template, C++ - template e programmazione genericaun template permette di usare un tipo (o un intero noto in compilazione) come parametro di una classe o di una funzione; il compilatore genera il codice per ogni tipo per cui il template è specializzato (template instantiation, a tempo di compilazione): il codice è efficiente ma gli errori sono lunghi e oscuri, e il compilatore controlla sia il template sia il suo argomento; la notazione è template(o class T, equivalenti) davanti alla classe o alla funzione, e le funzioni membro fuori dalla classe si scrivono template ) che sta nello stack; si inizializza con una lista di 20 valori:tipo Classe ::nome(...); nelle function template il compilatore deduce gli argomenti dai parametri della chiamata (myMax(i, j) invece di myMax (i, j)), ma due argomenti di tipo diverso per lo stesso T sono un errore; una specializzazione esplicita (template<> ...) fornisce il codice per un tipo particolare; i parametri interi (template<typename T, int N> struct array) danno array di dimensione fissa senza free store, più efficienti e che esprimono concetti come la tripletta RGB; poiché il compilatore deve vedere la definizione completa al punto d'uso, le definizioni si mettono nell'header (spesso in un file .hpp incluso in coda al .h).C++ - template e programmazione generica → array<double, kN> a {3.5, 12.25, ...}. Hasize()e si scorre con ilfora intervallo.- Array C dinamico senza
delete:new double[20]va affidato a un oggetto con distruttore che facciadelete[]:std::unique_ptr<double[]>. La specializzazione per array ([]nel tipo) chiamadelete[]e fornisceoperator[]. - Copia (non spostamento): il
std::arrayresta invariato. La soglia è unaconstexpr double kSoglia. Si usax > kSoglia(strettamente superiore: il valore10.0non si copia); un contatoreusatitiene il numero di elementi scritti (da 0 a 20: l'array C ha 20 posti, quindi non può traboccare). - Uscita:
aè nello stack,clibera da solo il buffer: nessunadeletenel codice, nessun leak.
#include <array>
#include <iostream>
#include <memory>
using namespace std;
int main()
{
constexpr int kN = 20;
constexpr double kSoglia = 10.0; // soglia costante
// 1. std::array di 20 reali "a piacere"
array<double, kN> a {3.5, 12.25, 8.0, 15.5, 9.75, 21.0, 4.5, 10.0, 11.5, 2.25,
18.0, 7.5, 13.75, 1.0, 10.5, 6.25, 19.5, 5.0, 14.25, 9.0};
// 2. array C dinamico di 20 double, gestito da unique_ptr<double[]>: delete[] automatica
unique_ptr<double[]> c(new double[kN]);
int usati = 0;
// 3. si scorre lo std::array e si COPIANO nell'array C i valori superiori alla soglia (l'array di partenza non cambia)
for (double x : a)
if (x > kSoglia) c[usati++] = x;
cout << "std::array: " << a.size() << " elementi (invariato)\n";
cout << "copiati " << usati << " valori > " << kSoglia << ":";
for (int i = 0; i < usati; ++i) cout << ' ' << c[i];
cout << '\n';
// 4. nessuna delete: lo unique_ptr libera da solo; lo std::array sta nello stack
return 0;
}std::array: 20 elementi (invariato)
copiati 9 valori > 10: 12.25 15.5 21 11.5 18 13.75 10.5 19.5 14.25Controllo: i valori maggiori di 10 nell'array di partenza sono 9 (12.25, 15.5, 21, 11.5, 18, 13.75, 10.5, 19.5, 14.25); il 10.0 presente nell'array non è copiato.
Alternative: std::copy_if(a.begin(), a.end(), c.get(), [](double x) { return x > kSoglia; }) fa la stessa copia con un algoritmo STL (la soglia come constexpr globale; il contatore si ricava dall'iteratore restituito). std::vector<double> sarebbe ancora più semplice, ma il testo vuole un array C dinamico.
Versione ripasso
- Passi:
array<double, kN>di 20 reali (nello stack); array C dinamico di 20doublegestito daunique_ptr<double[]>(RAII:delete[]automatica, nessunadeletenel codice); scorrere lostd::arraye copiare i valori soglia (l'originale non cambia);usaticonta gli elementi scritti.
constexpr int kN = 20; constexpr double kSoglia = 10.0;
array<double, kN> a {3.5, 12.25, 8.0, 15.5, 9.75, 21.0, 4.5, 10.0, 11.5, 2.25,
18.0, 7.5, 13.75, 1.0, 10.5, 6.25, 19.5, 5.0, 14.25, 9.0};
unique_ptr<double[]> c(new double[kN]);
int usati = 0;
for (double x : a) if (x > kSoglia) c[usati++] = x;- Uscita:
std::array: 20 elementi (invariato); copiati 9 valori :12.25 15.5 21 11.5 18 13.75 10.5 19.5 14.25(il10.0non si copia: stretto). Alternativa:copy_ifcon una lambda. - Errori tipici:
unique_ptr<double>invece diunique_ptr<double[]>(deletesu array);>=al posto di>;delete[]esplicita (vietata dal testo); non contare gli elementi usati; usarestd::vectorquando si chiede un array C.