In questa sede, tratteremo uno dei concetti fondamentali della buona programmazione in C++ : il principio del minor privilegio. Il principio del minor privilegio non è niente altro che settare i giusti permessi ad una variabile.
Nell'esempio proposto di seguito non viene utilizzato il principio del minor privilegio.
#include <iostream> using std::cout; using std::endl; void stampa( int * ); int main() { int number = 4; stampa( &number ); return 0; } void stampa( int *numero ) { cout << *numero << endl; }
Come possiamo vedere, oltre alla classica funzione main, abbiamo la funzione stampa che accetta come parametro un indirizzo di memoria di un intero. La funzione non restituisce niente ( tipo di ritorno void )
Adesso esploriamo la funzione stampa assieme al suo parametro. Notiamo subito che il parametro di stampa è un puntatore ad intero non costante. La principale istruzione presente nella funzione è la medesima:
cout << *numero << endl;
In altre parole stampa il valore a cui punta la variabile numero. Come abbiamo visto, la funzione ha il solo compito di stampare il valore a cui punta la variabile puntatore; in teoria numero può puntare ad un altro indirizzo e può anche cambiare il valore nell'indirizzo puntato; tutto questo a noi non serve! Serve solamente stampare il valore presente nell'indirizzo a cui punta. Settiamo il principio del minor privilegio.
Ottenendo questo risultato:
#include <iostream>
using std::cout;
using std::endl;
void stampa( const int * const );
int main()
{
int number = 4;
stampa( &number );
return 0;
}
void stampa( const int * const numero )
{
cout << *numero << endl;
}
Come è possibile vedere, il codice è rimasto invariato, se non per il nuovo parametro della funzione stampa.
const int * const numero
Tranquilli, tutti quei const non sono messi a caso. C'è un ordine ben preciso che tra poco andremo a scoprire. Inizio svelando un trucchetto. La dichiarazione va letta da destra a sinistra tralasciando lo *. Come tutti noi sapremo, numero è un puntatore; quindi il valore di numero è un indirizzo di una variabile ( in questo caso contiene l'indirizzo della variabile number, passata come argomento ). Concludendo, leggendo da destra verso sinistra, possiamo interpretare la dichiarazione nel seguente modo:
il valore di numero ( cioè il suo indirizzo di memoria ) è costante. Questo significa che, la variabile puntatore in questione, non può cambiare il suo indirizzo.
Inoltre, il valore presente nell'indirizzo di memoria a cui punta la variabile numero è anche esso costante. Quindi, la variabile puntatore in questione non può cambiare il valore presente nell'indirizzo a cui punta.
Un altro uso di const:
const int * numero;
leggendo da destra verso sinistra, numero ( quindi l'indirizzo a cui punta numero ) non è costante. Il valore a cui punta la variabile puntatore è costante.
Ecco un esempio:
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int var = 4;
int var1 = 1;
const int *varPtr; // varPtr non è costante. Il valore a cui punta varPtr invece lo è
varPtr = &var; // varPtr punta all'indirizzo di var
cout << "*varPtr = " << *varPtr << endl;
*varPtr = 3; // errore, non posso modificare il valore presente nell'indirizzo a cui punta varPtr, esso è costante
varPtr = &var1; // del tutto lecito. Il valore di varPtr non è costante
cout << "*varPtr = " << *varPtr << endl;
*varPtr = 4; // errore, non posso modificare il valore presente nell'indirizzo a cui punta varPtr, esso è costante
return 0;
}
Come è possibile vedere il valore di varPtr è variabile; il valore a cui punta varPtr è costante e quindi non modificabile. Un esempio di applicazione ad una funzione, potrebbe essere il seguente:
#include <iostream>
using std::cout;
void stampa ( const char * );
int main()
{
char string[] = "Questa e' una stringa\n";
stampa( string );
return 0;
}
void stampa( const char * stringa )
{
for( ; *stringa != '\0'; stringa++ )
cout << *stringa;
}
come è possibile notare stringa ad ogni fine istruzione, presente nel ciclo, punta all'indirizzo successivo fino a quando il valore presente nell'indirizzo è uguale a '\0', uscendo!
Analizziamo un ultimo caso:
char * const ptr;
Oramai il meccanismo dovrebbe essere chiaro. Lo ripetiamo per l'ultima volta: ptr ( l'indirizzo ) costante e il valore presente nell'indirizzo è variabile.
Un esempio immediato:
#include <iostream>
using std::cout;
#include <cctype>
using std::islower;
using std::isupper;
void converti ( char * const );
int main()
{
char string[] = "Questa e' una stringa\n";
converti( string );
cout << string;
return 0;
}
void converti( char * const stringa )
{
for ( unsigned short indice = 0; stringa[ indice ] != '\0'; indice++ )
{
if ( islower( stringa[ indice ] ) )
stringa[ indice ] = toupper( stringa[ indice ] );
else
stringa[ indice ] = tolower( stringa[ indice ] );
}
}
Da notare la variabile indice. Senza quella variabile, non avremmo potuto scorrere avanti array, perché l'indirizzo è costante.
Usate sempre il principio del minor privilegio, settando ad ogni variabile i giusti permessi, in
modo tale da proteggere SEMPRE i vostri dati. Tutto ciò ricadrà a vostro favore :)
P.S. C'è da dire che non è sempre utilizzabile e quindi applicabile la tecnica del minor privilegio. Const viene usato solamente per proteggere i dati. Se la nostra funzione avrà il bisogno di modificare i nostri dati? Semplice, non usiamo const! Un esempio pratico potrebbe essere il seguente:
#include <iostream>
using std::cout;
using std::endl;
void copia( char *, const char * );
int main()
{
const int max = 13;
char string1[ max ];
char string2[] = "ciao a tutti";
copia ( string1, string2 );
cout << string1 << endl;
return 0;
}
void copia( char * stringa1, const char * stringa2 )
{
for ( ; ( *stringa1 = *stringa2 ) != '\0'; stringa1++, stringa2++ );
}
Il primo parametro, come possiamo vedere, cambia il suo valore puntato e ad ogni ciclo punta all'indirizzo successivo. Il secondo parametro ( stringa2 ) non cambia il valore a cui punta ( infatti è costante ) bensì cambia l'indirizzo ad ogni fine ciclo ( infatti è variabile )
Aggiungi un commento