Les espaces de noms ont été introduits dans la norme C++ en 1995, et ils sont généralement définis comme ceci :
Un espace de noms définit une nouvelle portée. Les espaces de noms offrent un moyen d'éviter les collisions de noms.
Bien que les espaces de noms soient massivement utilisés dans le code C++ récent, la plupart du code plus ancien n'utilise pas cette facilité.
Après avoir exploré le code source de nombreux projets C++, voici quelques raisons courantes pour lesquelles les espaces de noms sont utilisés dans ces projets.
1- Éviter les collisions de noms.
Dans ce cas, leur utilisation profite principalement au compilateur ; elle n'apporte aucune valeur supplémentaire au développeur en termes de lisibilité ou de maintenabilité du code.
2- Modulariser le code
Les bibliothèques C++ modernes utilisent massivement les espaces de noms pour modulariser leur base de code, et elles adoptent l'approche « espace de noms par fonctionnalité ». Cette approche utilise les espaces de noms pour refléter l'ensemble des fonctionnalités. Elle place tous les éléments liés à une même fonctionnalité (et uniquement celle-ci) dans un même espace de noms. Il en résulte des espaces de noms à forte cohésion et forte modularité, avec un couplage minimal entre eux. Les éléments qui travaillent étroitement ensemble sont placés côte à côte.
Boost est un excellent exemple de regroupement par fonctionnalité ; il contient des milliers d'espaces de noms, chacun servant à regrouper une fonctionnalité spécifique.
3- L'espace de noms anonyme.
Un espace de noms sans nom évite le recours à des variables statiques globales. L'espace de noms anonyme que vous créez n'est accessible que dans le fichier où il est défini.
4- Contournement du problème des enums.
Les enums « traditionnelles » en C++ exportent leurs énumérateurs dans la portée environnante, ce qui peut entraîner des collisions de noms si deux enums différentes dans la même portée définissent des énumérateurs portant le même nom.
Dans un grand projet, rien ne garantit que deux enums distinctes n'utiliseront pas le même nom. Ce problème a été résolu en C++11 avec enum class, qui place implicitement les valeurs de l'enum dans la portée du nom de celle-ci.
Avant C++11, un contournement courant consistait à déclarer une enum dans un espace de noms. Par exemple, au lieu de déclarer une enum comme ceci :
enum status{
status_ok,
status_error
};on pouvait la déclarer dans un espace de noms :
namespace status{
enum status{
ok,
error
};
}De nombreux projets C++ utilisent cette astuce ; par exemple, le code source de l'Unreal Engine utilise largement cette technique.
5- Masquer les détails par convention
Pour les bibliothèques de templates dont le code est implémenté dans des fichiers d'en-tête, il est utile d'indiquer aux utilisateurs de la bibliothèque que certains types ne doivent pas être utilisés directement car ce sont des détails d'implémentation. En C#, le mot-clé « internal » remplit ce rôle, mais en C++ il n'y a aucun moyen de masquer les types publics aux utilisateurs de la bibliothèque.
Un idiome courant du C++ moderne, lancé par les développeurs des bibliothèques Boost, consiste à séparer dans un sous-espace de noms distinct, par convention nommé detail, les symboles qui font partie de l'implémentation de votre module (c'est-à-dire qui ne font pas partie de l'API publique) mais qui doivent être publiquement disponibles.
Par exemple, la documentation de Boost.Math précise que :
Les fonctions non destinées à être utilisées par les applications se trouvent dans boost::math::detail.Les espaces de noms inline introduits en C++11 offrent deux possibilités supplémentaires :
Lorsqu'un espace de noms inline est défini, une directive using est implicitement insérée dans son espace de noms englobant. Lors de la recherche d'un nom qualifié à travers l'espace de noms englobant, les membres de l'espace de noms inline sont introduits et trouvés par la directive using implicite, même si ce nom est déclaré dans l'espace de noms englobant.
Par exemple, si vous compilez le code suivant avec USE_INLINE_B défini, la sortie de l'exécutable résultant est 1 ; sinon, la sortie est 2.
namespace A {
#if USE_INLINE_B
inline
#endif
namespace B {
int foo(bool) { return 1; }
}
int foo(int) { return 2; }
}
int main(void) {
return A::foo(true);
}Voici deux utilisations supplémentaires des espaces de noms inline, comme expliqué dans ce document.
6- Utiliser les définitions d'espaces de noms inline dans l'instanciation et la spécialisation explicites
Vous pouvez explicitement instancier ou spécialiser chaque membre d'un espace de noms inline comme s'il était membre de son espace de noms englobant. La recherche de nom pour le template primaire d'une instanciation ou d'une spécialisation explicite dans un espace de noms, par exemple M, considère les espaces de noms inline dont l'ensemble des espaces englobants inclut M. Par exemple :
namespace L {
inline namespace M {
template <typename T> class C;
}
template <typename T> void f(T) { /*...*/ };
}
struct X { /*...*/ };
namespace L {
template<> class C<X> { /*...*/ }; //template specialization
}
int main()
{
L::C<X> r;
f(r); // fine, L is an associated namespace of C
}Dans cet exemple,
Mest un espace de noms inline de son espace de noms englobant
L, la classe
Cest membre de l'espace de noms inline
M, donc
Lest un espace de noms associé de la classe
C.
Les règles suivantes s'appliquent lorsque vous utilisez des définitions d'espaces de noms inline dans l'instanciation et la spécialisation explicites :
- Une instanciation explicite doit se trouver dans un espace de noms englobant du template primaire si le nom du template est qualifié ; sinon, elle doit se trouver dans l'espace de noms englobant le plus proche du template primaire ou dans un espace de noms de l'ensemble des espaces englobants.
- Une déclaration de spécialisation explicite doit d'abord être déclarée dans la portée d'espace de noms de l'espace de noms englobant le plus proche du template primaire, ou dans un espace de noms de l'ensemble des espaces englobants. Si la déclaration n'est pas une définition, elle peut être définie plus tard dans n'importe quel espace de noms englobant.
7- Utiliser les définitions d'espaces de noms inline pour la gestion des versions des bibliothèques
Avec les définitions d'espaces de noms inline, vous pouvez fournir une interface source commune pour une bibliothèque ayant plusieurs implémentations, et un utilisateur de la bibliothèque peut choisir une implémentation à associer à l'interface commune. L'exemple suivant illustre l'utilisation des espaces de noms inline pour la gestion des versions de bibliothèques avec spécialisation explicite.
//foo.h
#ifndef SOME_LIBRARY_FOO_H_
#define SOME_LIBRARY_FOO_H_
namespace SomeLibrary
{
#ifdef SOME_LIBRARY_USE_VERSION_2_
inline namespace version_2 { }
#else
inline namespace version_1 { }
#endif
namespace version_1 {
template <typename T> int foo(T a) {return 1;}
}
namespace version_2 {
template <typename T> int foo(T a) {return 2;}
}
}
#endif
//myFooCaller.C
#include <Foo.h>
#include <iostream>
struct MyIntWrapper { int x;};
//Specialize SomeLibrary::foo()
//Should specialize the correct version of foo()
namespace SomeLibrary {
template <> int foo(MyIntWrapper a) { return a.x;}
}
int main(void) {
using namespace SomeLibrary;
MyIntWrapper intWrap = { 4 };
std::cout << foo(intWrap) + foo(1.0) << std::endl;
}Si vous compilez cet exemple avec SOME_LIBRARY_USE_VERSION_2_ défini, la sortie de l'exécutable résultant est 6 ; sinon, la sortie est 5. Si l'appel de fonction
foo(intWrap)est qualifié avec l'un des espaces de noms inline, vous devez alors vous assurer que la spécialisation explicite est effective.
