web-dev-qa-db-fra.com

A quoi servent les espaces de noms en ligne?

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

(En outre, il m'est difficile de savoir ce qui se passe lorsqu'un namespace est déclaré inline dans une déclaration, mais pas dans toutes les déclarations, qui peuvent résider dans des fichiers différents. N'est-ce pas un problème?)

312
Walter

Les espaces de noms en ligne sont une fonctionnalité de versioning de bibliothèque semblable à versioning de symbole , 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 plateforme).

C'est un mécanisme par lequel un auteur de bibliothèque peut donner à un espace de noms imbriqué une apparence et agir comme si toutes ses déclarations étaient dans l'espace de noms environnant (les espaces de noms en ligne peuvent être imbriqués, de sorte que les noms "plus imbriqués" se retrouvent jusqu'au premier non -inline namespace et regardez et agissez comme si leurs déclarations étaient aussi dans l'un des espaces de noms situés entre les deux).

A titre d'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 implémentation de vector est choisie. Si votre base de code a été écrite dans des versions antérieures à C++ 98 et que la version C++ 98 de vector vous cause des problèmes lorsque vous mettez à niveau votre compilateur, vous devez "tout" trouvez les références à std::vector dans votre base de code et remplacez-les par std::pre_cxx_1997::vector.

Passez au prochain standard et le fournisseur STL répète simplement la procédure, en introduisant un nouvel espace de noms pour std::vector avec le support emplace_back (qui requiert C++ 11) et en y insérant un iff __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

Selon 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 déjà permis de spécialiser entièrement les modèles qui résident dans l'espace de noms std dans 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

Il s'agit d'un code parfaitement valide dans lequel l'utilisateur fournit sa propre implémentation d'un vecteur pour un ensemble de types où il connaît apparemment une implémentation plus efficace que celle trouvée dans (sa copie) du 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é. La norme indique que vector est déclaré dans namespace std, de sorte 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 intégré C++ 11, mais pas avec l'astuce de gestion des versions qui utilisait using namespace <nested>, car elle expose les détails de l'implémentation que le véritable espace de noms dans lequel vector était défini n'était pas std directement.

Il existe d'autres trous permettant de détecter l'espace de noms imbriqué (voir les commentaires ci-dessous), mais les espaces de noms en ligne les connectent tous. Et c'est tout ce qu'il y a à faire. Immensément utile pour l’avenir, mais selon les indications de AfAIK, le standard ne prescrit pas de noms d’espace de nommage en ligne pour sa propre bibliothèque standard (je serais cependant ravi de me tromper à ce sujet), il ne peut donc être utilisé que par des bibliothèques tierces. la norme elle-même (à moins que les fournisseurs du compilateur s’entendent sur un schéma de nommage).

322
Marc Mutz - mmutz

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 à la la plupart des fonctionnalités de C++ 11.)

Selon cela, il est prévu d'autoriser le versioning pour la compatibilité ascendante. Vous définissez plusieurs espaces de nom 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 tranchante 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 tout de suite pourquoi vous ne mettiez pas using namespace V99; dans l'espace de noms Mine, mais je n'ai pas besoin de comprendre parfaitement le cas d'utilisation pour pouvoir prendre le mot de Bjarne, sur la motivation du comité. .

63
Steve Jessop

En plus de toutes les réponses ci-dessus.

L'espace de nom 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 tronqué (ABI) sans modifier l'API, car ils affectent uniquement le nom du symbole de l'éditeur de liens.

Considérons cet exemple:

Supposons que vous écriviez une fonction Foo prenant une référence à un objet, par exemple bar et ne renvoyant 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 va certainement coder le nom des types de fonction et d'argument quelque part.

Maintenant, il se pourrait que bar soit défini comme suit:

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

Selon le type de construction, bar peut faire référence à deux types/présentations différents avec les mêmes symboles de l'éditeur de liens.

Pour éviter un tel comportement, nous encapsulons notre struct bar dans un espace de nom intégré, où, en fonction du type de construction, le symbole de l'éditeur de liens de bar sera différent.

Donc, nous pourrions é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 flag de débogage. Vous constaterez que les symboles de l'éditeur de liens incluent également le nom d'espace de nom intégré. Dans ce cas

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

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

Notez la présence de rel et dbg dans les noms de symboles.

Maintenant, si vous essayez de lier le débogage avec le mode de publication ou inversement, vous obtiendrez une erreur de l'éditeur de liens, contrairement à l'erreur d'exécution.

2
coder3101

En fait, j'ai découvert une autre utilisation des espaces de nom en ligne.

Avec Qt , vous obtenez des fonctionnalités supplémentaires, Nice utilisant Q_ENUM_NS , ce qui nécessite à son tour que le namespace englobant ait un méta-objet, qui est déclaré avec _Q_NAMESPACE_. Cependant, pour que _Q_ENUM_NS_ fonctionne, il doit y avoir un _Q_NAMESPACE_ correspondant dans le même fichier ¹⁾. Et il ne peut y en avoir qu'un, sinon 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. Le fait de masquer des énumérations dans un _inline namespace_ fait en sorte que les méta-objets ont différents noms mutilés, tandis que les utilisateurs considèrent que l'espace de noms supplémentaire n'existe pas².

Donc, ils sont utiles pour diviser des éléments en plusieurs sous-espaces de noms qui ressemblent tous à un seul, si vous avez besoin de le faire pour une raison quelconque. Bien sûr, cela ressemble à écrire _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.


  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.

0
Matthew