Maurizio Tomasi
Martedì 12 Novembre 2024
std::vector
std::vector
Finora abbiamo sempre usato std::vector
per
memorizzare sequenze di valori, e si potrebbero usare anche
nell’esercitazione di oggi:
L’uso di std::vector
ha però una serie di svantaggi
in questo contesto.
Creare un nuovo oggetto std::vector
è un’operazione
lenta! Internamente std::vector
crea un
puntatore a un array:
Ma new
richiede molto lavoro da parte del sistema
operativo, che deve ogni volta individuare un’area di memoria non
occupata da altre variabili.
È impossibile per il compilatore sapere se due variabili
std::vector
hanno lo stesso numero di elementi
Dobbiamo verificare noi a mano che le dimensioni di vettori siano compatibili:
Dimenticarsi un assert
da qualche parte nel codice
potrebbe avere conseguenze disastrose (soprattutto durante
l’esame!)
Siccome std::vector
può contenere un numero
variabile di elementi, è difficile capire su quante dimensioni lavori
una classe derivata da FunzioneVettorialeBase
:
struct FunzioneVettorialeBase {
virtual vector<double> Eval(const vector<double> &x) = 0;
};
struct OscillatoreArmonico : FunzioneVettorialeBase {
vector<double> Eval(const vector<double> &x) override {
// Lo spazio delle fasi è a 2 dimensioni, ma questo è chiaro solo
// se si contano gli elementi del vettore restituito!
return vector<double>{x[1], -x[0]};
}
};
std::array
std::array
La libreria standard offre una classe perfetta per gli esercizi
di oggi: std::array
.
Essa è equivalente ad un array con un numero fissato di elementi, che vengono controllati in fase di compilazione.
Gli array sono allocati nella memoria stack, che è velocissima da usare ma limitata. Dovrebbero essere usati solo se il numero di elementi non supera qualche decina.
Un’implementazione semplificata di std::array
è la
seguente, che non usa new
:
Se il valore N
è ridotto a pochi elementi (due o
tre), il compilatore può addirittura decidere di evitare di usare lo
stack ed impiega invece i registri (che è il tipo di
memoria usabile dalla CPU più veloce in assoluto)
Finora abbiamo sempre visto typename T
nei template,
dicendo che il tipo (double
, float
,
int
…) deve essere fornito quando si istanzia il
template:
Usare size_t N
indica che nella definizione di
std::array
, il valore numerico di N
è un
intero senza segno da fornire quando si istanzia il template:
std::array
Quando si istanzia std::array
, bisogna specificare
anche la dimensione:
std::vector<double> v(2); // The size is 2, but it can change later
std::array<double, 2> a; // The size is 2 and *cannot* change
v[0] = 0.5; v.at(1) = 1.0; // Both std::vector and std::array can be
a[0] = 0.7; a.at(1) = 2.0; // accessed using [] or .at()
v.push_back(3.14159); // Ok, the array will grow
a.push_back(2.71828); // ERROR, you cannot use push_back with an array
Si può inizializzare un array in maniera immediata:
L’anno scorso ho proposto agli alunni più volonterosi di usare
std::array
, per questi motivi:
Il codice è circa 10 volte più veloce, e questo è importante soprattutto se si devono fare Monte Carlo di problemi con equazioni differenziali;
In sede di esame vediamo a volte errori di dimensionalità, che
std::array
previene;
Non è necessario implementare controlli sulla dimensione degli array, perché ci pensa il compilatore.
Questo è il primo anno in cui ho aggiornato la pagina carminati-esercizi-08.html perché
usi std::array
!
Se combino due array con lunghezza diversa
std::array x{1.0, 2.0}; // An array with 2 elements
std::array y{3.0, 4.0, 5.0}; // An array with 3 elements
std::array z{x + y}; // Gosh, what's going to happen?
il compilatore g++
produce un errore di
compilazione:
test_arr.cpp:64:13: error: no match for ‘operator+’ (operand types are ‘std::array<double, 2>’ and ‘std::array<double, 3>’)
64 | array z{x + y};
| ~ ^ ~
| | |
| | array<[...],3>
| array<[...],2>
Nel caso di std::vector
, il codice compilerebbe ma
andrebbe poi in crash.
Con tutte le dimensioni esplicitate, il codice diventa più leggibile
Ad esempio, nel derivare la classe
OscillatoreArmonico
dell’esercizio 8.1
bisogna specificare <2>
per
FunzioneVettorialeBase
:
Similmente, nel main
dello stesso si definisce
l’istanza di Eulero
così:
Negli scorsi anni ho proposto un seminario di approfondimento su C++, Assembler, Python e Julia, una volta terminata la sessione di esami di Febbraio.
Nel seminario spiego le differenze tra di loro, mostro come sono progettati i rispettivi compilatori, e do indicazioni su come scegliere lo strumento di lavoro migliore.
Se siete interessati, compilate il Google Form all’indirizzo https://forms.gle/saJjERRmYR6KxPpe9: alla fine del semestre contatterò chi l’ha compilato per decidere la data migliore per tutti.