À quoi servent les espaces de noms en ligne?

334

C ++ 11 autorise les inline namespaces, dont tous les membres sont également automatiquement inclus dans l'enveloppe namespace. Je ne peux penser à aucune application utile de ceci - quelqu'un peut-il s'il vous plaît donner un exemple bref et succinct d'une situation où un inline namespaceest nécessaire et où c'est la solution la plus idiomatique?

(De plus, il n'est pas clair pour moi ce qui se passe quand a namespaceest déclaré inlinedans une mais pas dans toutes les déclarations, qui peuvent vivre dans des fichiers différents. N'est-ce pas un problème?)

Walter
la source

Réponses:

339

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 vectorimplé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 vectorvous pose problème lorsque vous mettez à niveau votre compilateur, "tout" vous devez faire est de trouver les références à std::vectordans 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::vectoravec emplace_backsupport (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 stdde 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 vectorest 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 stddirectement.

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).

Marc Mutz - mmutz
la source
23
+1 pour expliquer pourquoi using namespace V99;ne fonctionne pas dans l'exemple de Stroustrup.
Steve Jessop
3
De même, si je démarre une toute nouvelle implémentation C ++ 21 à partir de zéro, je ne veux pas être surchargé d'implémenter beaucoup de vieux non-sens 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.
Steve Jessop
46
Ce n'est pas tout. ADL était également une raison (ADL ne suivra pas l'utilisation de directives), et la recherche de nom aussi. ( using namespace Adans 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 recherchez B::name- ce n'est pas le cas avec les espaces de noms en ligne).
Johannes Schaub - litb
4
Pourquoi ne pas simplement utiliser ifdefs 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étraitement
sasha.sochka
6
@ sasha.sochka, car dans ce cas, vous ne pouvez pas utiliser d'autres implémentations. Ils seront supprimés par le préprocesseur. Avec les espaces de noms en ligne, vous pouvez utiliser n'importe quelle implémentation de votre choix en spécifiant un nom complet (ou un usingmot clé).
Vasily Biryukov
70

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:

// file V99.h:
inline namespace V99 {
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file V98.h:
namespace V98 {
    void f(int);    // does something
    // ...
}

// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}

#include "Mine.h"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);       // default version

Je ne vois pas immédiatement pourquoi vous ne mettez pas à l' using namespace V99;intérieur de l'espace de noms Mine, 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é.

Steve Jessop
la source
Donc, en fait, la dernière f(1)version serait appelée depuis l' V99espace de noms en ligne ?
Eitan T
1
@EitanT: oui, car l'espace de noms global a using namespace Mine;, et l' Mineespace de noms contient tout de l'espace de noms en ligne Mine::V99.
Steve Jessop
2
@Walter: vous supprimez inlinedu fichier V99.hdans la version qui inclut V100.h. Vous modifiez également Mine.hen même temps, bien sûr, pour ajouter une inclusion supplémentaire. Mine.hfait partie de la bibliothèque, pas du code client.
Steve Jessop
5
@walter: ils n'installent pas 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.het V99.h. Il y a 4 fichiers d' en- tête dans la version 100 de "Mine" - Mine.h, V98.h, V99.het V100.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::fpartir de tout ou partie de leur code, ils peuvent mélanger les appels à Mine::V98::fpartir de l'ancien code avec des appels à Mine::fdu code nouvellement écrit.
Steve Jessop
2
@Walter Comme l'autre réponse le mentionne, les modèles doivent être spécialisés dans l'espace de noms dans lequel ils sont déclarés, pas dans un espace de noms utilisant celui dans lequel ils sont déclarés. Mine, au lieu de se spécialiser dans Mine::V99ou Mine::V98.
Justin Time - Rétablir Monica
8

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 Fooqui prend une référence à un objet, par exemple, baret ne renvoie rien.

Dites dans main.cpp

struct bar;
void Foo(bar& ref);

Si vous vérifiez le nom de votre symbole pour ce fichier après l'avoir compilé dans un objet.

$ nm main.o
T__ Z1fooRK6bar 

Le nom du symbole de l'éditeur de liens peut varier, mais il encodera sûrement le nom des types de fonction et d'argument quelque part.

Maintenant, il se pourrait que cela barsoit défini comme:

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

Selon le type de construction, barpeut 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 bardans un espace de noms en ligne, où, selon le type de construction, le symbole de l'éditeur de liens barsera différent.

On pourrait donc écrire:

#ifndef NDEBUG
inline namespace rel { 
#else
inline namespace dbg {
#endif
struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};
}

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

$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar

Les noms des symboles de l'éditeur de liens peuvent être différents.

Remarquez la présence de relet dbgdans 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.

coder3101
la source
1
Oui, cela a du sens. C'est donc plus pour les implémenteurs de bibliothèque et similaires.
Walter
3

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é avec Q_NAMESPACE. Cependant, pour Q_ENUM_NSfonctionner, il doit y avoir un correspondant Q_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 uninline namespacefait 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 innerdans l'espace de noms externe, mais sans la violation DRY d'écrire deux fois le nom de l'espace de noms interne.


  1. C'est en fait pire que ça; il doit être dans le même ensemble d'accolades.

  2. 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.

Matthieu
la source
Pouvez-vous esquisser cela avec un squelette de code? (idéalement sans référence explicite à Qt). Tout cela semble plutôt complexe / peu clair.
Walter
Pas facilement. La raison pour laquelle des espaces de noms séparés sont nécessaires est liée aux détails de l'implémentation de Qt. TBH, il est difficile d'imaginer une situation en dehors de Qt qui aurait les mêmes exigences. Cependant, pour ce scénario spécifique à Qt, ils sont sacrément utiles! Voir gist.github.com/mwoehlke-kitware/… ou github.com/Kitware/seal-tk/pull/45 pour un exemple.
Matthew
0

Donc, pour résumer les principaux points, using namespace v99et inline namespacen'é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'utilisation using, tout en fournissant la même fonctionnalité de gestion des versions. L'utilisation using namespaceutilisée pour causer des problèmes avec ADL (bien qu'ADL semble maintenant suivre les usingdirectives) 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).

//library code
namespace B { //library name the user knows
    namespace A { //ABI version the user doesn't know about
        template<class T> class myclass{int a;};
    }
    using namespace A; //pre inline-namespace versioning trick
} 

// user code
namespace B { //user thinks the library uses this namespace
    template<> class myclass<int> {};
}

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).

#include <iostream>
namespace A {
    namespace B{
        int a;
        int func(int a);
        template<class T> class myclass{int a;};
        class C;
        extern int d;
    } 
    using namespace B;
} 
int A::d = 3; //No member named 'd' in namespace A
class A::C {int a;}; //no class named 'C' in namespace 'A' 
template<> class A::myclass<int> {}; // works; specialisation is not an out-of-line definition of a declaration
int A::func(int a){return a;}; //out-of-line definition of 'func' does not match any declaration in namespace 'A'
namespace A { int func(int a){return a;};} //works
int main() {
    A::a =1; // works; not an out-of-line definition
}

Le problème disparaît lors de la création de B en ligne.

Les autres fonctionnalités des inlineespaces 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:

namespace library {
    inline namespace abi_v1 {
        class foo {
        } 
    }
}

Il permet à l'utilisateur d'appeler library::foosans avoir besoin de connaître ou d'inclure la version ABI dans la documentation, qui semble plus propre. L'utilisation library::abiverison129389123::fooaurait 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 parce library::fooque ça marchera toujours.

namespace library {
    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

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_v1erreur 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:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

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_v1elle 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érences library::abi_v2.

L'utilisation using namespaceest moins fonctionnelle que l'utilisation inline(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.

Lewis Kelsey
la source