Salta al contenuto
Note per Studenti Esercizio - Copiare da array a array dinamico senza delete (terzo appello 2022-23, programmazione)

Esercizio - Copiare da array a array dinamico senza delete (terzo appello 2022-23, programmazione)

Esame

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 ) sono oggetti che detengono un puntatore e lo deallocano automaticamente all'uscita dallo scope (RAII), eliminando memory leak e dangling pointer con un overhead minimo. unique_ptr ha un solo proprietario: non si copia (costruttore e assegnamento di copia cancellati) ma si sposta (std::move, ritorno da funzione), ha reset(), release(), get(), * e ->; due unique_ptr costruiti con lo stesso puntatore grezzo sono un errore (doppia delete) che il compilatore non vede, e si evita con make_unique. shared_ptr ha più proprietari: si copia, tiene un contatore di riferimenti (use_count) e libera l'oggetto quando l'ultimo shared_ptr viene distrutto, a un costo maggiore; weak_ptr osserva senza possedere, per rompere i cicli. auto_ptr è deprecato (rimosso in C++17).C++ - smart pointer →, 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 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 → (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::array di valori reali e lo popoli usando 20 valori a piacere; 2. allochi dinamicamente un array stile C di dimensione 20; 3. scorra lo std::array e copi nell'array stile C i valori superiori a una soglia costante definita opportunamente; 4. esca senza usare esplicitamente delete/delete[] nel codice dell'esercizio e senza causare memory leak.

Ragionamento

  1. 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 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 →) che sta nello stack; si inizializza con una lista di 20 valori: array<double, kN> a {3.5, 12.25, ...}. Ha size() e si scorre con il for a intervallo.
  2. Array C dinamico senza delete: new double[20] va affidato a un oggetto con distruttore che faccia delete[]: std::unique_ptr<double[]>. La specializzazione per array ([] nel tipo) chiama delete[] e fornisce operator[].
  3. Copia (non spostamento): il std::array resta invariato. La soglia è una constexpr double kSoglia. Si usa x > kSoglia (strettamente superiore: il valore 10.0 non si copia); un contatore usati tiene il numero di elementi scritti (da 0 a 20: l'array C ha 20 posti, quindi non può traboccare).
  4. Uscita: a è nello stack, c libera da solo il buffer: nessuna delete nel codice, nessun leak.
list_to_array2.cppcpp
#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.25

Controllo: 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 20 double gestito da unique_ptr<double[]> (RAII: delete[] automatica, nessuna delete nel codice); scorrere lo std::array e copiare i valori >> soglia (l'originale non cambia); usati conta gli elementi scritti.
cpp
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 >10>10: 12.25 15.5 21 11.5 18 13.75 10.5 19.5 14.25 (il 10.0 non si copia: >> stretto). Alternativa: copy_if con una lambda.
  • Errori tipici: unique_ptr<double> invece di unique_ptr<double[]> (delete su array); >= al posto di >; delete[] esplicita (vietata dal testo); non contare gli elementi usati; usare std::vector quando si chiede un array C.

Esercizi su questo argomento

Teoria collegata