Usare const correttamente in C e C++
In questa lezione userai la parola chiave const per dichiarare dati di sola lettura e distinguerai i diversi usi di const con i puntatori.
Introduzione
La parola chiave const permette a una funzione di promettere che non modificherà i dati che riceve. Se qualcuno modifica per sbaglio un dato marcato const, il compilatore segnala subito l'errore. Così il codice è più sicuro e chi legge capisce facilmente quali funzioni possono modificare i dati.
Punti importanti
- Un puntatore deve puntare solo a un oggetto che esiste ancora, del tipo giusto, oppure essere nullo.
- Ogni area di memoria allocata va liberata esattamente una volta.
- Rendi chiaro quale funzione possiede la memoria (ownership) e quali dati sono solo letti (const).
Esempi di codice C e C++
#include <stdio.h>
int sum(const int *values, size_t count) {
int total = 0;
for (size_t i = 0; i < count; ++i) total += values[i];
return total;
}
int main(void) {
const int values[] = {2, 4, 6};
printf("%d\n", sum(values, 3));
return 0;
}
12
#include <array>
#include <iostream>
int sum(const std::array<int, 3>& values) {
int total = 0;
for (int value : values) total += value;
return total;
}
int main() {
const std::array values{2, 4, 6};
std::cout << sum(values) << '\n';
}
12
Confronto tra C e C++
Il parametro const int *values indica che la funzione sum legge solo gli elementi dell'array senza modificarli. Conviene leggere la dichiarazione partendo dal nome della variabile: un puntatore a dato const (const int *) protegge il valore puntato, mentre un puntatore const (int * const) non può puntare a un altro indirizzo. C++ ha anche le funzioni membro const, che promettono di non cambiare lo stato dell'oggetto.
Il possesso e la liberazione della memoria sono interamente a carico del programmatore.
Contenitori e RAII liberano automaticamente le risorse quando l'oggetto arriva alla fine della sua vita.
Esercizi di approfondimento
Esegui entrambe le versioni e poi modifica il codice per osservare la differenza di sicurezza che offre ciascun linguaggio.
- Prova con puntatori nulli e allocazioni di memoria fallite.
- Esegui il programma con AddressSanitizer e UndefinedBehaviorSanitizer per trovare errori di memoria.
- Annota chiaramente quale puntatore possiede la memoria e quale serve solo per leggere.