Les espaces de noms en ligne sont une fonctionnalité de versionnage de bibliothèque semblable à la version de symboles , mais implémentée uniquement au niveau C ++ 11 (c'est-à-dire multiplateforme) au lieu d'être une fonctionnalité d'un format exécutable binaire spécifique (c'est-à-dire spécifique à la plate-forme).
C'est un mécanisme par lequel un auteur de bibliothèque peut faire ressembler un espace de noms imbriqué et agir comme si toutes ses déclarations se trouvaient dans l'espace de noms environnant (les espaces de noms en ligne peuvent être imbriqués, donc les noms "plus imbriqués" s'infiltrent jusqu'au premier non -inline namespace et regardez et agissez comme si leurs déclarations se trouvaient également dans l'un des espaces de noms).
Par exemple, considérons l'implémentation STL de vector
. Si nous avions des espaces de noms en ligne depuis le début de C ++, alors en C ++ 98 l'en-tête <vector>
aurait pu ressembler à ceci:
namespace std {
#if __cplusplus < 1997L // pre-standard C++
inline
#endif
namespace pre_cxx_1997 {
template <class T> __vector_impl; // implementation class
template <class T> // e.g. w/o allocator argument
class vector : __vector_impl<T> { // private inheritance
// ...
};
}
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
# if __cplusplus == 1997L // C++98/03
inline
# endif
namespace cxx_1997 {
// std::vector now has an allocator argument
template <class T, class Alloc=std::allocator<T> >
class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
// ...
};
// and vector<bool> is special:
template <class Alloc=std::allocator<bool> >
class vector<bool> {
// ...
};
};
#endif // C++98/03 or later
} // namespace std
En fonction de la valeur de __cplusplus
, l'une ou l'autre vector
implémentation est choisie. Si votre base de code a été écrite en pré-C ++ 98 fois et que vous constatez que la version C ++ 98 vector
vous pose problème lorsque vous mettez à niveau votre compilateur, "tout" vous devez faire est de trouver les références à std::vector
dans votre base de code et remplacez-les par std::pre_cxx_1997::vector
.
Venez la prochaine norme, et le fournisseur STL répète simplement la procédure à nouveau, en introduisant un nouvel espace de noms pour std::vector
avec emplace_back
support (qui nécessite C ++ 11) et en l'incluant __cplusplus == 201103L
.
OK, alors pourquoi ai-je besoin d'une nouvelle fonctionnalité de langue pour cela? Je peux déjà faire ce qui suit pour avoir le même effet, non?
namespace std {
namespace pre_cxx_1997 {
// ...
}
#if __cplusplus < 1997L // pre-standard C++
using namespace pre_cxx_1997;
#endif
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
namespace cxx_1997 {
// ...
};
# if __cplusplus == 1997L // C++98/03
using namespace cxx_1997;
# endif
#endif // C++98/03 or later
} // namespace std
En fonction de la valeur de __cplusplus
, j'obtiens l'une ou l'autre des implémentations.
Et vous auriez presque raison.
Considérez le code utilisateur C ++ 98 valide suivant (il était autorisé de spécialiser complètement les modèles qui vivent déjà dans l'espace std
de noms en C ++ 98):
// I don't trust my STL vendor to do this optimisation, so force these
// specializations myself:
namespace std {
template <>
class vector<MyType> : my_special_vector<MyType> {
// ...
};
template <>
class vector<MyOtherType> : my_special_vector<MyOtherType> {
// ...
};
// ...etc...
} // namespace std
C'est un code parfaitement valide où l'utilisateur fournit sa propre implémentation d'un vecteur pour un ensemble de type où il connaît apparemment une implémentation plus efficace que celle trouvée dans (sa copie de) la STL.
Mais : lors de la spécialisation d'un modèle, vous devez le faire dans l'espace de noms dans lequel il a été déclaré. Le standard indique qu'il vector
est déclaré dans l'espace de noms std
, c'est donc là que l'utilisateur s'attend à juste titre à spécialiser le type.
Ce code fonctionne avec un espace de noms non versionné std
, ou avec la fonctionnalité d'espace de noms en ligne C ++ 11, mais pas avec l'astuce de versioning utilisée using namespace <nested>
, car cela expose les détails d'implémentation que le véritable espace de noms dans lequel a vector
été défini n'était pas std
directement.
Il existe d'autres trous par lesquels vous pouvez détecter l'espace de noms imbriqué (voir les commentaires ci-dessous), mais les espaces de noms en ligne les bouchent tous. Et c'est tout ce qu'il y a à faire. Immensément utile pour l'avenir, mais AFAIK the Standard ne prescrit pas de noms d'espace de noms en ligne pour sa propre bibliothèque standard (j'aimerais bien me tromper à ce sujet), donc il ne peut être utilisé que pour des bibliothèques tierces, pas la norme elle-même (à moins que les fournisseurs du compilateur ne conviennent d'un schéma de dénomination).
using namespace V99;
ne fonctionne pas dans l'exemple de Stroustrup.std::cxx_11
. Tous les compilateurs n'implémenteront pas toujours toutes les anciennes versions des bibliothèques standard, même s'il est tentant pour le moment de penser qu'il serait très peu contraignant d'exiger que les implémentations existantes restent dans l'ancienne lors de l'ajout de la nouvelle, car en fait, elles ont toutes sont de toute façon. Je suppose que ce que la norme aurait pu utilement faire est rendu facultatif, mais avec un nom standard s'il est présent.using namespace A
dans un espace de noms B fait des noms dans l'espace de noms B masque les noms dans l'espace de noms A si vous recherchezB::name
- ce n'est pas le cas avec les espaces de noms en ligne).ifdef
s pour l'implémentation vectorielle complète? Toutes les implémentations seraient dans un seul espace de noms mais une seule d'entre elles sera définie après le prétraitementusing
mot clé).http://www.stroustrup.com/C++11FAQ.html#inline-namespace (un document écrit et maintenu par Bjarne Stroustrup, qui, selon vous, devrait connaître la plupart des motivations de la plupart des fonctionnalités C ++ 11. )
Selon cela, il s'agit d'autoriser la gestion des versions pour une compatibilité descendante. Vous définissez plusieurs espaces de noms internes et créez le plus récent
inline
. Ou de toute façon, celui par défaut pour les personnes qui ne se soucient pas de la gestion des versions. Je suppose que la plus récente pourrait être une version future ou de pointe qui n'est pas encore par défaut.L'exemple donné est:
Je ne vois pas immédiatement pourquoi vous ne mettez pas à l'
using namespace V99;
intérieur de l'espace de nomsMine
, mais je n'ai pas à comprendre entièrement le cas d'utilisation afin de prendre le mot de Bjarne pour cela sur la motivation du comité.la source
f(1)
version serait appelée depuis l'V99
espace de noms en ligne ?using namespace Mine;
, et l'Mine
espace de noms contient tout de l'espace de noms en ligneMine::V99
.inline
du fichierV99.h
dans la version qui inclutV100.h
. Vous modifiez égalementMine.h
en même temps, bien sûr, pour ajouter une inclusion supplémentaire.Mine.h
fait partie de la bibliothèque, pas du code client.V100.h
, ils installent une bibliothèque appelée "Mine". Il existe 3 fichiers d'en-tête dans la version 99 de "Mine" -Mine.h
,V98.h
etV99.h
. Il y a 4 fichiers d' en- tête dans la version 100 de "Mine" -Mine.h
,V98.h
,V99.h
etV100.h
. L'agencement des fichiers d'en-tête est un détail d'implémentation qui n'est pas pertinent pour les utilisateurs. S'ils découvrent un problème de compatibilité qui signifie qu'ils doivent utiliser spécifiquement àMine::V98::f
partir de tout ou partie de leur code, ils peuvent mélanger les appels àMine::V98::f
partir de l'ancien code avec des appels àMine::f
du code nouvellement écrit.Mine
, au lieu de se spécialiser dansMine::V99
ouMine::V98
.En plus de toutes les autres réponses.
L'espace de noms en ligne peut être utilisé pour coder les informations ABI ou la version des fonctions dans les symboles. C'est pour cette raison qu'ils sont utilisés pour fournir une compatibilité ABI en amont. Les espaces de noms en ligne vous permettent d'injecter des informations dans le nom modifié (ABI) sans modifier l'API car ils affectent uniquement le nom du symbole de l'éditeur de liens.
Considérez cet exemple:
Supposons que vous écriviez une fonction
Foo
qui prend une référence à un objet, par exemple,bar
et ne renvoie rien.Dites dans main.cpp
Si vous vérifiez le nom de votre symbole pour ce fichier après l'avoir compilé dans un objet.
Maintenant, il se pourrait que cela
bar
soit défini comme:Selon le type de construction,
bar
peut faire référence à deux types / mises en page différents avec les mêmes symboles de l'éditeur de liens.Pour éviter un tel comportement, nous encapsulons notre structure
bar
dans un espace de noms en ligne, où, selon le type de construction, le symbole de l'éditeur de liensbar
sera différent.On pourrait donc écrire:
Maintenant, si vous regardez le fichier objet de chaque objet, vous en créez un en utilisant release et un autre avec un indicateur de débogage. Vous constaterez que les symboles de l'éditeur de liens incluent également le nom de l'espace de noms en ligne. Dans ce cas
Remarquez la présence de
rel
etdbg
dans les noms de symboles.Maintenant, si vous essayez de lier le débogage au mode de libération ou vice versa, vous obtiendrez une erreur de l'éditeur de liens contrairement à l'erreur d'exécution.
la source
J'ai en fait découvert une autre utilisation des espaces de noms en ligne.
Avec Qt , vous obtenez des fonctionnalités supplémentaires et agréables à utiliser
Q_ENUM_NS
, ce qui nécessite à son tour que l'espace de noms englobant ait un méta-objet, qui est déclaré avecQ_NAMESPACE
. Cependant, pourQ_ENUM_NS
fonctionner, il doit y avoir un correspondantQ_NAMESPACE
dans le même fichier ⁽¹⁾. Et il ne peut y en avoir qu'un, ou vous obtenez des erreurs de définition en double. Cela signifie que toutes vos énumérations doivent être dans le même en-tête. Beurk.Ou ... vous pouvez utiliser des espaces de noms en ligne. Masquer les énumérations dans un
inline namespace
fait que les méta-objets ont des noms différents modifiés, tandis que la recherche d'utilisateurs comme l'espace de noms supplémentaire n'existe pas⁽²⁾.Ils sont donc utiles pour diviser des éléments en plusieurs sous-espaces de noms qui ressemblent tous à un seul espace de noms, si vous devez le faire pour une raison quelconque. Bien sûr, cela est similaire à l'écriture
using namespace inner
dans l'espace de noms externe, mais sans la violation DRY d'écrire deux fois le nom de l'espace de noms interne.C'est en fait pire que ça; il doit être dans le même ensemble d'accolades.
Sauf si vous essayez d'accéder au méta-objet sans le qualifier complètement, mais le méta-objet n'est presque jamais utilisé directement.
la source
Donc, pour résumer les principaux points,
using namespace v99
etinline namespace
n'étaient pas les mêmes, le premier était une solution de contournement pour les bibliothèques de versions avant qu'un mot clé dédié (en ligne) ne soit introduit en C ++ 11 qui résolvait les problèmes d'utilisationusing
, tout en fournissant la même fonctionnalité de gestion des versions. L'utilisationusing namespace
utilisée pour causer des problèmes avec ADL (bien qu'ADL semble maintenant suivre lesusing
directives) et la spécialisation hors ligne d'une classe / fonction de bibliothèque, etc. par l'utilisateur ne fonctionneraient pas si elles étaient effectuées en dehors du véritable espace de noms (dont le nom l'utilisateur ne devrait pas et ne devrait pas savoir, c'est-à-dire que l'utilisateur devrait utiliser B :: abi_v2 :: plutôt que juste B :: pour la spécialisation à résoudre).Cela affichera un avertissement d'analyse statique
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Mais si vous créez l'espace de noms A en ligne, le compilateur résout correctement la spécialisation. Bien qu'avec les extensions C ++ 11, le problème disparaisse.Les définitions hors ligne ne se résolvent pas lors de l'utilisation
using
; ils doivent être déclarés dans un bloc d'espace de noms d'extension imbriqué / non imbriqué (ce qui signifie que l'utilisateur doit connaître à nouveau la version ABI, si pour une raison quelconque il était autorisé à fournir sa propre implémentation d'une fonction).Le problème disparaît lors de la création de B en ligne.
Les autres fonctionnalités des
inline
espaces de noms permettent au rédacteur de la bibliothèque de fournir une mise à jour transparente à la bibliothèque 1) sans forcer l'utilisateur à refactoriser le code avec le nouveau nom de l'espace de noms et 2) éviter le manque de verbosité et 3) fournir l'abstraction des détails non pertinents pour l'API, tandis que 4) donner les mêmes diagnostics et comportements de l'éditeur de liens bénéfiques que l'utilisation d'un espace de noms non en ligne fournirait. Disons que vous utilisez une bibliothèque:Il permet à l'utilisateur d'appeler
library::foo
sans avoir besoin de connaître ou d'inclure la version ABI dans la documentation, qui semble plus propre. L'utilisationlibrary::abiverison129389123::foo
aurait l'air sale.Lorsqu'une mise à jour est effectuée
foo
, c'est- à -dire l'ajout d'un nouveau membre à la classe, cela n'affectera pas les programmes existants au niveau de l'API car ils n'utiliseront pas déjà le membre ET la modification du nom de l'espace de noms en ligne ne changera rien au niveau de l'API parcelibrary::foo
que ça marchera toujours.Cependant, pour les programmes qui se lient à lui, car le nom d'espace de noms en ligne est modifié en noms de symboles comme un espace de noms normal, la modification ne sera pas transparente pour l'éditeur de liens. Par conséquent, si l'application n'est pas recompilée mais est liée à une nouvelle version de la bibliothèque, elle présentera une
abi_v1
erreur de symbole non trouvé, plutôt qu'elle se lie réellement et provoque une mystérieuse erreur logique au moment de l'exécution en raison d'une incompatibilité ABI. L'ajout d'un nouveau membre entraînera la compatibilité ABI en raison du changement de définition de type, même s'il n'affecte pas le programme au moment de la compilation (niveau API).Dans ce scénario:
Comme l'utilisation de 2 espaces de noms non en ligne, cela permet de lier une nouvelle version de la bibliothèque sans avoir à recompiler l'application, car
abi_v1
elle sera altérée dans l'un des symboles globaux et elle utilisera la définition de type correcte (ancienne). La recompilation de l'application entraînerait cependant la résolution des référenceslibrary::abi_v2
.L'utilisation
using namespace
est moins fonctionnelle que l'utilisationinline
(dans la mesure où les définitions hors ligne ne se résolvent pas) mais offre les mêmes 4 avantages que ci-dessus. Mais la vraie question est de savoir pourquoi continuer à utiliser une solution de contournement alors qu'il existe maintenant un mot clé dédié pour le faire. C'est une meilleure pratique, moins verbeuse (il faut changer 1 ligne de code au lieu de 2) et rend l'intention claire.la source