[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> C++
> Lecture du tutoriel
Les espaces de nom (namespace)
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Alors, les espaces de noms, qu'est-ce que c'est ? A quoi ça peut bien servir? Je vais essayer de vous expliquer le plus clairement possible comment créer son propre espace de nom, comment les utiliser, mais également leur utilité.
Vous en avez tous déjà utilisé un. En effet, les fonctions d'entrée-sortie standard du C++ sont situées dans un espace de nom : std. Vous l'utilisez chaque fois que vous utilisez les fonctions "cin" et "cout" par exemple, mais savez-vous pourquoi on utilise un espace de nom?
Si vous ne savez pas, alors vous êtes au bon endroit!
Pour lire ce tutoriel, vous devez connaître le langage C++. Si ce n'est pas le cas, vous pouvez lire le très bon tutoriel sur ce langage écrit par M@teo21.
Je ne peux pas commencer à vous expliquer comment les faire sans démarrer par la base : qu'est-ce que c'est, et pourquoi peut-on en avoir besoin?
La réponse est simple : un espace de nom, c'est un ensemble qui regroupe des variables et des fonctions, des classes, tout ce que vous voulez dans un même ensemble. Je vais vous présenter l'utilité d'un espace de nom et pour le faire, rien de mieux qu'un exemple :
Fichier main.cpp :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11 | #include <iostream>
using namespace std; //On utilise un espace de nom ici
const int valeur=5;
int main(int argc, char* argv[])
{
cout << "Valeur = " << valeur << endl;
return 0;
}
|
Fichier second.cpp :
Code : C++1
2 | const int valeur=2;
//Plein de fonctions
|
Comme vous le voyez ici, dans le fichier main.cpp, on déclare une variable globale valeur. Et dans le fichier second.cpp, on déclare également une variable globale valeur. Si on compile notre projet avec ces 2 fichiers, on aura le droit à une magnifique erreur nous disant que "valeur" à déjà été définie dans main.cpp.
Oui mais il ne sert à rien ton exemple. On a pas idée de déclarer 2 variables globales avec le même nom. Et en plus on sait très bien qu'il faut éviter les globales. Pourquoi tu nous montres ça?
Bonne question. Il s'agit d'un exemple bête pour montrer le principe, je ne vais pas écrire tout un code complexe pour montrer leur utilité si je peux le faire avec un code tout simple et accessible à tous.
Revenons donc au sujet : dans mon exemple, le compilo provoque une erreur car la variable est déclaré 2 fois avec le même nom. Comment contourner ce problème ? Il suffit d'utiliser un espace de nom. Si les 2 variables sont situés dans un espace de nom différent, elles peuvent très bien avoir le même nom, le compilo sait laquelle des deux variables il doit choisir car on lui dira "je veux la variable valeur situé dans tel espace de nom".
L'intêret de base est ici. Evidemment, dans l'exemple, c'est assez stupide d'utiliser un espace de nom dans un tel programme. Mais maintenant imaginez que vous soyez en train de coder une bibliothèque. Vous ne pouvez pas vous permettre de "bloquer" l'utilisation de certains noms à l'utilisateur de votre bibliothèque. Exemple : sans espace de nom, si dans votre biblio vous utilisez une classe Event, l'utilisateur n'aura pas le droit de créer de classe Event, ce qui impose au passage que l'utilisateur connaisse la composition du code source de votre bibliothèque pour savoir quel mot-clé il n'a pas le droit d'utiliser.
Avec un namespace, le problème est contourné. On créé un namespace pour sa bibliothèque, et voilà le travail, l'utilisateur peut utiliser tout les noms qu'il veut, il ne risque pas le conflit. La SFML (pour ne citer qu'elle) utilise ce système. Elle est écrite dans un namespace : sf.
Dans le langage C, les espaces de noms n'existant pas, le problème était contourné en préfixant les variables et les fonctions appartenant à une même bibliothèque. Pour prendre un exemple que vous connaissez grâce au cours de m@teo21, les fonctions de la SDL sont toutes de la forme SDL_NomDeLaFonction. La création des espaces de noms met fin à cette préfixation forcée.
Maintenant que vous comprenez un peu mieux leur utilité, voyons comment on peut utiliser notre propre namespace.
Si vous lisez ces lignes, c'est que leur utilisation vous intéresse, et je vous rassure, elle n'a rien de bien difficile. Il suffit juste de savoir comment créer son propre espace de nom et le tour est joué.
Rentrons directement dans le vif du sujet. Pour créer un namespace, rien de plus simple:
Code : C++1
2
3
4 | namespace NomDuNamespace
{
//Contenu du namespace
}
|
Prenez note du fait qu'il n'y pas de point virgule après l'accolade fermante.
Je la mets où ma déclaration, et je mets quoi dedans?
Les déclarations de namespace se mettent dans les fichiers header(.h ou .hpp) et dans les fichiers source(.cpp). Pour ce qui est du contenu, encore une fois on reste dans la simplicité, vous mettez ce que vous mettriez habituellement dans votre fichier .h, .hpp ou .cpp. Rien de tel qu'un exemple pour comprendre:
Fichier perso.hpp:
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #ifndef PERSO_HPP
#define PERSO_HPP
namespace Vanger
{
class Perso
{
private:
//Mes attributs
public:
//Le prototype de mes méthodes
};
//Le prototype des mes fonctions
int carre(int);
//Des variables
int valeur=5;
} //Fin du namespace
#endif
|
Fichier perso.cpp:
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | #include "perso.hpp"
namespace Vanger
{
//Mes fonctions, méthodes
Perso::Perso()
{
std::cout << "Creation d'un perso" << std::endl;
}
int carre(int valeur)
{
return valeur*valeur;
}
} //Fin du namespace
|
Comme vous le voyez, rien ne change, on se contente dans les fichiers .hpp d'écrire nos classes, nos prototypes de fonctions, nos variables (et l'attribution de leur valeur). Et dans le fichier .cpp, on écrit le corps de nos fonctions, méthodes, etc.. Comme vous le feriez normalement. La seule différente réside dans le fait que vous le faites à l'intérieur d'un bloc "namespace Nomdevotrename".
Ne pas attribuer de valeur à vos variables à l'intérieur du bloc namespace situé dans vos fichier .cpp. Vous provoquerez une erreur de type "Redefinition of...", ce qui signifie que vous redéfinissez une variable déjà définie.
Pour modifier la variable "valeur" du namespace "Vanger" par exemple, il faut accéder à la variable comme ceci:
Code : C++
Dans l'exemple je crée donc un namespace Vanger qui contient une fonction carre, une variable valeur et une classe Perso.
Tout les éléments contenus dans un namespace sont publics. On ne peut pas modifier leur type d'accés.
Ainsi, utiliser le mot clef private dans un namespace comme ceci:
Code : C++1
2
3
4 | namespace Vanger
{
private const int valeur; //Tente de rendre privé la variable valeur
}
|
Cela ne fonctionne pas. Les éléments d'un namespace sont obligatoirement publics. Attention également, vous ne pouvez pas dans votre fichier .cpp écrire une fonction comme ceci, car le corps d'une fonction doit être écrit à l'intérieur du namespace:
Code : C++1
2
3
4 | int Vanger::carre(int valeur)
{
return valeur*valeur;
}
|
Votre compilateur refusera de créer l'éxécutable dans ces cas là. Si vous vous demandez ce qu'est le "Vanger::" devant la fonction carre, c'est tout simplement le moyen d'accéder au contenu du namespace Vanger, comme nous allons le voir dès maintenant.
C'est bien beau de créer un espace de nom, encore faut-il savoir y accéder. Et là encore, tout le monde sait le faire car tout le monde l'a déjà fait au moins une fois dans son passé de codeur C++. On le fait régulièrement avec le namespace std (standard au C++).
Pour accéder, rien de plus simple :
Code : C++
Dans cette exemple, je fais appel à la fonction carre situé dans l'espace de nom que j'ai créé dans la partie précédente. Comme vous le voyez, j'ai juste eu à ajouter "NomduNamespace::" devant l'appel à la fonction et le tour est joué. Le système est exactement le même pour accéder à une classe ou à une variable.
Code : C++
Un autre moyen d'utiliser un namespace est le mot-clé "using". Avec celui-ci, vous n'avez plus à écrire le nomdunamespace:: devant chaque appel aux fonctions d'un espace de nom donné.
Vous l'utilisez tous très souvent comme ceci:
Code : C++
Vous placez généralement cette ligne avant le main. Ce qu'il faut savoir, c'est que vous pouvez la placer n'importe où. Elle agira alors dans le bloc où elle à été placée. Si vous la mettez en dehors de tout bloc (par exemple avant le main), elle agira comme une variable globale statique (c'est-à-dire sur l'ensemble du fichier dans lequel elle a été déclarée), mais si vous la placez dans un bloc "if" par exemple, la condition ne sera valable que pour le if en question.
Code : C 1
2
3
4
5
6
7
8
9
10
11 | int x=0;
bool continuer=true;
if(continuer)
{
using namespace Vanger;
x+=carre(x); //Ici, le programme va chercher carre dans le namespace Vanger
}
else
{
x+=carre(x); //Erreur du compilo : fonction carre non déclarée.
}
|
Dans le premier bloc du if, je déclare travailler dans le namespace Vanger, donc le programme va chercher la fonction carre dans ce namespace. Dans le deuxième bloc, il ne sait pas où chercher car le "using namespace Vanger" n'est pas valable pour ce bloc. Résultat : la fonction carre n'est pas déclarée.
Si il existe également une fonction "carre" avec la même signature de fonction (la signature d'une fonction se basant sur son nom et le type de ses paramètres) en dehors du namespace Vanger, l'utilisation du mot-clé using peut être source de problèmes. En effet, il y a aura alors conflit entre les deux fonctions, le compilateur renvoie donc une erreur pour cause d'ambiguïté.
Ce n'est pas le cas pour les variables. Ainsi, il est tout à fait possible, dans le cas du namespace Vanger toujours, d'écrire un main comme ceci:
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12 | #include <iostream>
#include "vanger.h"
using namespace std;
using namespace Vanger;
int main(int argc, char* argv[])
{
int valeur=8;
cout << valeur << endl;
return 0;
}
|
Je peux donc redéfinir une variable "valeur" malgré que celle-ci existe dans le namespace. Dans le "cout" qui suit, la variable valeur utilisée étant celle définie dans le main et non celle du namespace. Les variables situées dans le namespace sont utilisées en dernier recours si le compilateur ne trouve pas la variable dans le bloc en cours ou un bloc parent (si vous avez du mal à comprendre, vous pouvez revoir la portée des variables).
Voilà, vous savez utilisé votre espace de nom ainsi que le mot-clé "using".
Dans cette petite partie, je vais aborder les espaces de noms anonymes. Pourquoi est-ce qu'on les appelle anonymes? Tout simplement parce qu'ils sont définis sans nom. Ces namespaces particuliers s'écrivent directement dans le fichier où ils seront utilisés. Un exemple vaut bien mieux qu'un long discours:
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #include <iostream>
using namespace std;
namespace
{
int carre(int valeur)
{
return valeur*valeur;
}
int valeur=5;
}
int main(int argc, char* argv[])
{
cout << carre(valeur) << endl;
return 0;
}
|
Pour définir l'espace de nom, c'est toujours le même système. La seule différence réside dans le fait que celui-ci n'a plus de nom.
Mais si il n'a plus de nom, comment on fait pour accéder à ce qu'il contient? On peut plus faire NomDuNamespace:: ? Et pour le mot-clé using?
On ne les utilises tout simplement pas. Son contenu est directement accessible comme vous pouvez le voir dans le code précédent. Il me suffit d'appeler la fonction "carre" et le compilateur va la chercher dans le namespace anonyme. MAIS, son contenu n'est disponible que dans le fichier où celui-ci à été déclaré, et là est tout l'intérêt.
Il s'agit en réalité du même comportement que l'utilisation du mot-clé static (qui permet de limiter la portée d'une variable au fichier où elle a été déclarée). Seulement, dans le langage C++, l'utilisation de namespace anonyme est privilégiée sur l'utilisation du mot-clé static.
Les remarques sur la redéfinition de fonctions et de variables de la sous-partie précédente sont également valables dans le cas d'espace de nom anonyme
Ce tutoriel sur les namespaces touche à sa fin. Vous savez désormais comment créer et utiliser vos propres espaces de noms. Sachez également qu'on peut très bien imbriquer des espaces de noms les uns dans les autres de la même façon qu'expliquée dans ce tutoriel, la seule chose importante est d'être bien organisé.
Et en parlant d'être organisé, vous l'aurez compris maintenant, le principe d'un espace de nom, c'est d'organiser son code. Et maintenant, vous pouvez le faire.
Bon codage à vous tous et j'espère que ce tutoriel pourra vous servir un jour!