Namespace

16 gennaio 2017

Più familiarizziamo con un linguaggio di programmazione e più tendiamo ad assumere una certa pratica nel prevenire gli errori più banali. Ad esempio, una delle cose che impariamo subito è che non possiamo usare nessuna delle parole riservate del linguaggio per nominare i simboli del nostro programma.

Sappiamo, inoltre, che dobbiamo evitare di usare due volte lo stesso nome nello stesso ambito di visibilità, e che dobbiamo stare attenti a non oscurare una variabile “esterna” creandone una nuova.

Siamo quindi abituati a tutta una serie di vincoli e restrizioni che, anche se non in maniera severa, ci condizionano nella scelta dei nomi di variabili e funzioni, e di tutte le altre entità che vedremo meglio nel seguito.

Tuttavia, non siamo gli unici a decidere quali nomi usare.

Pensiamo ad esempio alle operazioni di I/O per le quali ci serviamo di librerie già esistenti. Anche in questo caso, infatti, escludiamo tutti i simboli definiti in esse dal bacino delle nostre possibilità. In caso di conflitti, quindi, può essere necessario un pesante intervento di refactoring per risolvere quello che dovrebbe essere un banale problema di integrazione.

Per questo motivo, C++ mette a disposizione un costrutto apposito per la definizione di gruppi di nomi indipendenti gli uni dagli altri: i namespace.

La sintassi per la definizione di un namespace è la seguente:

namespace <nome>
{
	// definizione dei simboli
	... 
}		

I simboli definiti dentro il namespace sono protetti da ingerenze esterne e allo stesso modo evitano di “invadere” lo spazio dei nomi di altre librerie. Lo stesso namespace può essere usato più volte, ad esempio quando i nostri simboli sono sparsi su più di un file, avendo cura di usare sempre lo stesso nome (rispettando maiuscole e minuscole). La figura seguente mostra, a titolo esemplificativo, l’uso dello stesso namespace test su più file, anche ripetuto più volte nello stesso file.

L’esempio successivo mostra una semplice applicazione dei namespace. Supponiamo di voler implementare due funzioni di utilità per la stampa di numeri in formato esadecimale e binario. Possiamo definire due namespace distinti, Hex e Bin, per le nostre funzioni di stampa. Per entrambe possiamo permetterci di usare lo stesso nome, printNumber, per dare un certo carattere di omogeneità alle nostre API.

#include <iostream>
#include <bitset>

namespace Hex
{
	int base = 16;
	void printNumber(int value)
	{
		std::cout << std::hex << value << std::dec << std::endl;
	}
}

namespace Bin 
{
	int base = 2;
	void printNumber(int value)
	{
		std::cout << std::bitset<32>(value) << std::endl;
	}
}

int main()
{
	int a = 123546;

	Hex::printNumber(a);
	Bin::printNumber(a);

	std::cout << Hex::base << std::endl;
	std::cout << Bin::base << std::endl;

	return 0;
}

Per invocare le due funzioni nel main usiamo l’operatore di risoluzione di scope, identificato dai caratteri ::, che ci consente di accedere ai simboli definiti entro i rispettivi namespace.

Tralasciamo per il momento i dettagli implementativi delle due funzioni, che riguardano le varie opzioni di I/O della libreria iostream e quelle di bitset per la manipolazione di bit, per le quali si rimanda all’apposita documentazione.

La vita utile delle variabili definite dentro un namespace, al pari di quelle globali, concide con l’intera durata del programma. Ciò significa che esse risiedono nei segmenti di memoria Data segment o BSS a seconda che siano state inizializzate o meno. In questo caso, le due variabili base, definite nei rispettivi namespace, sono quindi sempre disponibili in qualunque contesto.

Risolvere le ambiguità dell’uso di namespace

Nell’esempio precedente l’invocazione diretta della funzione printNumber, senza cioè fare riferimento ad uno dei namespace in cui sono definite, produrrebbe un errore di compilazione, poichè il compilatore cercherebbe una funzione con questo nome definita in ambito globale.

Alcuni compilatori, ad esempio g++, potrebbero segnalare la presenza di funzioni candidabili, cioè con questo nome, definite all’interno di namespace, ma la compilazione verrebbe interrotta ugualmente poichè, anche in questo caso, il compilatore da solo non saprebbe quale delle due usare.

Tuttavia, a volte non è necessario fare riferimento nello stesso frammento di codice a simboli contenuti in namespace differenti, ed in questo caso, il continuo uso dell’operatore di risoluzione di scope sarebbe superfluo, oltre che prolisso.

A questo scopo è utile ricordare la clausola using namespace, la quale indica in maniera esplicita al compilatore di tenere in considerazione i simboli definiti in un namespace durante la compilazione, così da risolvere le possibili ambiguità.

Nell’esempio successivo, modifichiamo il contenuto della funzione main con l’uso di questa clausola. Nel primo ambito di visibilità, nel quale usiamo il namespace Hex, la prima invocazione di printNumber stamperà un valore esadecimale. Successivamente, creando un nuovo ambito di visibilità all’interno del quale invece imponiamo l’uso dei simboli definiti in Bin, stamperà una sequenza di bit.

// definizione dei namespace Hex e Bin
...	

int main()
{
	int a = 123546;
	
	{
		using namespace Hex;	
		printNumber(a); // esacedimale
	}     
		
	{
		using namespace Bin;
		printNumber(a); // binario
	}

	return 0;
}

Questo esempio funziona perchè i due ambiti di visibilità sono indipendenti. Se fossero stati annidati, vi sarebbe stata comunque una intersezione tra i due spazi di nomi Hex e Bin, visto l’uso del medesimo simbolo printNumber, e pertanto sarebbe stato necessario ricorrere nuovamente all’uso dell’operatore ::.

L’uso frequente dei simboli definiti nella libreria iostream fornisce un altro esempio dell’utilità di using namespace. Nell’esempio precedente, aggiungere la clausola seguente ci avrebbe consentito di rimuovere ogni occorrenza del prefisso std::, e di ottenere quindi un listato più leggibile.

// include
...

using namespace std;

// definizione dei namespace Hex e Bin
...

È inoltre possibile restringere l’applicazione della clausola using a singoli elementi del namespace, con una clausola del tipo:

using Hex::base;

In questo caso possiamo omettere il prefisso Hex:: per il solo simbolo base, mentre per specificare quale funzione printNumber usare si dovrà ricorrere all’operatore di risoluzione di scope, così come per accedere alla variabile base definita in Bin.

Tutte le lezioni

1 ... 14 15 16 ... 58

Se vuoi aggiornamenti su Namespace inserisci la tua e-mail nel box qui sotto:
Tags:
 
X
Se vuoi aggiornamenti su Namespace

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy