Dal C al C++/Utilizzo basilare di librerie/L'uso dei namespace

Wikibooks, manuali e libri di testo liberi.
Indice del libro

Notoriamente, quando si vuole utilizzare una libreria, si devono prima dichiarare le funzioni esportate (cioè pubblicate) da tale libreria.

Normalmente, invece di inserire a mano nel proprio codice le dichiarazioni delle funzioni che si intende utilizzare, ci si limita a include uno (o più) file di intestazione che contiene le dichiarazioni necessarie, usando la direttiva "#include" del preprocessore. Tipicamente, tali file di intestazione non contengono solo dichiarazioni di funzioni, ma anche di variabili, costanti, tipi, strutture e, in C++, classi, template di classe e template di funzione. Insomma, includendo un file di intestazione si mette a disposizione del programmatore una gran quantità di identificatori.

Quando il compilatore trova un identificatore, cerca tale identificatore tra tutti quelli che sono stati dichiarati in precedenza in quella unità di compilazione e che sono attivi in quell'ambito (scope).

Dal punto di vista del compilatore, gli identificatori vengono cercati in una struttura dati che tradizionalmente si chiama "symbol table", anche se non ha necessariamente la forma di una tabella. Ogni volta che si dichiara un identificatore, si aggiunge una voce alla symbol table.

Dal punto di vista del programmatore, gli identificatori vengono cercati nello "spazio dei nomi", o, in inglese, "namespace". Ogni volta che si dichiara un identificatore, si aggiunge una voce allo spazio dei nomi corrente.

Nel linguaggio C, i nomi hanno un ambito, cioè possono essere locali o globali, ma gli oggetti globali appartengono tutti allo stesso spazio dei nomi.

Questo fatto crea problemi quando si vuole incominciare ad usare una nuova libreria in un programma esistente, o, peggio, quando si vogliono usare due o più librerie progettate indipendentemente.

Supponiamo che stiamo sviluppando un software che riguarda l'elettronica o l'elettrotecnica, e di usare per una funzione l'identificatore "socket" per indicare, in inglese, un dispositivo fisico a cui fa riferimento l'applicazione.

Ad un certo punto vogliamo aggiungere all'applicazione una funzionalità di comunicazione di rete, e utilizziamo una libreria che usa il medesimo nome "socket" per indicare un canale di comunicazione. Abbiamo una collisione di nomi, dalla quale si può uscire effettuando una ricerca e sostituzione globale nella libreria o nella nostra applicazione. Supponendo di non avere i sorgenti della libreria, l'unica possibilità è modificare l'applicazione.

La situazione è peggiore se abbiamo adottato due librerie non modificabili che utilizzano lo stesso nome per concetti diversi. In tal caso il conflitto di nomi è insanabile.

È per rimediare a tale situazione che in C++ sono stati introdotti i "namespace".

Ogni libreria ben progettata dovrebbe inserire tutti i nomi che dichiara all'interno di un namespace. Per esempio, la famosa libreria Boost, inserisce tutte le proprie dichiarazioni nel namespace "boost".

La stessa libreria standard del C++ inserisce gran parte delle proprie dichiarazioni nel namespace "std" (che significa "standard").

Per accedere a un identificatore contenuto in un namespace da una funzione dichiarata all'interno del namespace stesso, non è richiesta nessuna sintassi particolare. Quindi, chi sviluppa una libreria può dimenticarsi che gli identificatori della libreria verranno posti in un namespace.

Invece, per accedere a un identificatore contenuto in un namespace da una funzione esterna al namespace stesso, si deve invece qualificare l'identificatore, facendolo precedere da nome del namespace, separato da due caratteri ":". Per esempio, se dal codice applicativo si vuole usare l'oggetto "cout" e la classe "string" si può usare il seguente codice:

std::string s = "abc";
std::cout << s;

In tal modo, si comunica al compilatore che, anche se nel codice applicativo o in una libreria non standard fossero definiti un identificatore "string" e un identificatore "cout", non si vuol far riferimento a tali identificatori, ma a quelli facenti parte della libreria standard.

Per evitare di dover ripetere il nome del namespace ogni volta che si fa riferimento a un identificatore di libreria, si può usare l'apposita direttiva seguente, dove N rappresenta il nome di un namespace:

using namespace N;

Dopo tale direttiva, tutti i nomi contenuti nel namespace N possono essere utilizzati senza necessità di qualificazione. Pertanto è appropriato porre una dichiarazione di questo tipo appena dopo le direttive di inclusione di ogni unità di compilazione che usa qualche identificatore di tale namespace.

Ovviamente, nei casi, non molto comuni, in cui in uno stesso file si usano due o più librerie che dichiarano uno stesso nome, non si potrà usare tale direttiva in tale file, in quanto provocherebbe un conflitto. In tali casi, si dovrà ricorrere alla qualificazione esplicita.

La direttiva "using namespace" non dovrebbe però essere usata mai né nei file di intestazione, né in una unità di compilazione prima di una direttiva di inclusione. Infatti, usandola prima di una direttiva di inclusione verrebbe applicata al contenuto del file incluso, e dato che un file incluso potrebbe includere direttamente o indirettamente molti altri file, applicandola a un file incluso potrebbe comportare che venga applicata ad alcuni file in cui provocherebbe conflitto.