web-dev-qa-db-fra.com

C ++ espaces de noms extensibles - Comment forcer les déclarations dans l'espace de noms global

C'est un bon style de programmation d'inclure toutes les dépendances nécessaires dans une en-tête qui les référencent. Cela inclut souvent des déclarations placées dans les espaces de noms STD & GLOBAL (comme CSTDIO). Toutefois, cela crée des problèmes lorsqu'un second programmeur souhaite envelopper un fichier d'inclure dans un nouvel espace de noms pour encapsuler le premier.

Dans ce scénario à l'esprit, est-il un moyen de forcer les déclarations dans l'espace de nom mondial? Comme exemple conceptuel (cela ne compilera pas réellement):

foo.h --> original definition
// force global namespace, this is redundant when foo is in the global space
//   but could be important if foo included into another namespace.
namespace :: { 
  #include <cstdio>
}
namespace foo {
  FILE *somefile;
}


bar.h --> New file, by a new programmer, encapsulating foo.
namespace bar {
  # include "foo.h"
  FILE *somefile;  // bar:somefile, as opposed to bar::foo::somefile
}

Sans le putatif namespace ::{} Nous nous retrouvons avec des déclarations telles que foo::FILE, Et foo::printf(), lorsque nous voulons juste ::FILE Et ::printf().

Les bibliothèques populaires telles que - Boost Résolvez ceci en définissant une hiérarchie explicite. Cela nécessiterait que FOO sache que cela fera partie de la barre et placera l'espace de noms global incluant en dehors de la définition des espaces de noms locaux.

Cependant, une bonne extensibilité nécessite que nous puissions utiliser FOO directement, OR Wrap FOO dans un nouvel espace de noms, sans avoir à changer de foo ou de son inclut.

Est ce que quelqu'un sait comment accomplir cela?

6
bgulko

Y compris un en-tête dans un espace de noms est enclin à briser diverses hypothèses et ne devrait pas être faite. Les problèmes qui viennent à l'esprit sont:

  • Il casse des gardes. Si un en-tête ne peut être inclus qu'une seule fois, et vous l'inclurez dans le mauvais espace de noms, le code ne peut pas l'inclure dans l'espace de noms correct dans la même unité de compilation. Ou l'inverse ronde: si elle était déjà incluse par une bibliothèque, vous ne pouvez pas le ré-inclure ailleurs. Par exemple.:

    somelib/a.h

    #ifndef SOMELIB_A_H_
    #define SOMELIB_A_H_
    // assume this will be included into namespace someLib
    class A {};
    #endif
    

    somelib/b.h

    #ifndef SOMELIB_B_H_
    #define SOMELIB_B_H_
    namespace someLib
    {
        # include "A.h"
        int b(const A& a) { return 42; }
    }
    #endif
    

    userercode.cpp

    // defines someLib::b and somelib::A
    #include <somelib/B.h>
    // include A into global namespace
    // – won't work because someLib/A.h was already included
    #include <somelib/A.h>
    #include <iostream>
    int main()
    {
        std::cout << someLib::b(A()) << '\n';
        return 0;
    }
    

    Cela échouera parce que #include <someLib/A.h> est un non-op dans le programme principal et n'inclut rien. Par conséquent, A est indéfini dans le programme principal. Si les fichiers étaient inclus dans l'autre sens, cela fonctionnerait depuis le someLib::b Déclaration peut également voir des symboles dans la portée mondiale. Cependant, l'ordre d'inclure ne devrait pas compter dans une bibliothèque bien conçue!

  • Vous ne pourrez pas lier. La plupart des fichiers d'en-tête ont un fichier .CPP associé qui inclut les détails de la mise en œuvre privés. Si vous incluez un en-tête dans un espace de noms différent, il ne peut pas être associé à la mise en œuvre.

  • Il enfreint les macros. Si une macro fournissait une bibliothèque dans une bibliothèque, il devra toujours utiliser le nom qualifié de pas de noms entièrement de noms, car nous ne savons pas dans quel espace de noms qu'il sera utilisé. Si vous parvenez à inclure un symbole foo::bar::BazException comme qux::BazException, alors une macro parfaitement raisonnable telle que

    #define FOO_BAR_BAZEXCEPTION(expr) foo::bar::BazException(expr, __FILE__, __LINE__)
    

    arrêtera de travailler.

  • Il brise la recherche dépendante de l'argument, qui ne fonctionne que si la fonction et ses arguments partagent un espace de noms.

Si l'inclusion d'un fichier dans un espace de noms est une idée horrible et allumera plus de choses que ce qu'elle fixe, quelles sont les alternatives?

Tout d'abord, les espaces de noms sont bons. Ils nous aident à donner une structure à l'architecture globale, même s'ils sont très ouverts. Nous utilisons des espaces de noms non seulement parce qu'ils imposent une hiérarchie pratique qui évite les affrontements de nom, mais également parce que certaines fonctionnalités telles que l'argument-dépassent la recherche dépendent de celle-ci. La seule raison pour laquelle la bibliothèque standard met certains symboles dans la portée globale est la compatibilité avec C. Même alors, vous pouvez accéder aux symboles via le std:: Espace de noms, qui devrait être préféré.

Certaines personnes estiment que les espaces de noms pourraient devenir gênés longtemps. Ce n'est pas un problème car nous pouvons définir des alias plus courts dans la portée où ils sont nécessaires avec using foo::bar::Baz. C'est également la solution lors de la mise en place de symboles dans la portée globale: un alias ne masque pas le nom d'origine, entièrement qualifié, de sorte que tous les problèmes mentionnés ci-dessus ne s'appliquent pas. La directive using a beaucoup d'utilisations et peut alias espace de noms, noms de passe et valeurs telles que des variables ou des fonctions membres.

Lorsque vous incluez un fichier, supposez toujours qu'il est correctement ppété. Par conséquent, incluez-la dans la portée mondiale avant d'ouvrir n'importe quel espace de noms. Même si les en-têtes proviennent de votre propre projet, assurez-vous qu'ils déclarent leurs propres espaces de noms. Cela signifie également que la structure des espaces de noms est assez difficile à changer une fois qu'elle est corrigée. Mais c'est bien - toute modification de votre interface publique (qui inclut la structure de l'espace de noms) nécessiterait également que tout code dépendant change. Je ne vois pas comment "une bonne extensibilité" nous obligerait à abandonner cette structure.

9
amon

La réponse courte semble être que les hiérarchies d'espace de noms ont été conçues pour être définies statiquement au moment de la conception et non utilisées dans une conception étendue. C'est-à-dire, non disponible pour envelopper dans d'autres espaces de noms après la conception. Ainsi, ils sont plus un outil permettant de prévenir son propre code de polluer l'espace global, plutôt que de contenir une pollution des espaces de noms auprès du code des autres personnes. Je crois que cette limitation n'est pas nécessaire, mais cela clarifie mon enquête immédiate.

Certains travaux supplémentaires ont donné une solution partielle, mais la réponse courte est que les espaces de noms n'ont pas été conçus pour être imbriqués après Design. Utilisez-les de cette façon, même si possible, est à contre-courant à la conception de l'intention de conception et donc une mauvaise idée. Cela dit, voici une méthode pour y parvenir.

Je crois que la plupart des problèmes liés à la liaison pourraient être résolus par deux pratiques:

  1. S'assurer que les incluses non lignes ont été accueillies dans l'espace de noms global.
  2. Seulement étendre les espaces de noms en ligne/en-tête.

Le premier pourrait être effectué maintenant en créant un fichier nom_ext.h pour chaque espace de noms et y compris à la portée mondiale. Un espace de noms dérivé aurait son propre dérivé_ext.h qui incluait tous les noms nom_exuels dépendants.H, employant donc des obstacles à plusieurs inconvénients pour assurer que la société mondiale a été accueillie dans l'espace de noms global. Cela préserverait la liaison de bibliothèque binaire. UNE namespace :: {} Extension des définitions à domicile dans l'espace de noms global de l'intérieur d'un bloc d'espace de noms existant, réparerait cela plus élégamment.

Le deuxième point traite de la liaison de temps de compilation des fichiers .CPP. J'ai testé cela et ça marche.

Le seul problème restant est celui des classes multipliées. Sans qu'un soutien substantiel de préprocesseur pour les barrières dynamiques incluent, il resterait impossible de disposer d'espaces de noms multi-hébergés, ce qui est finalement un problème réel pour la conception d'orthogonalité.

Cependant, cela serait toujours limité aux espaces de noms entièrement contenus dans les fichiers incluant.

En tout état de cause en utilisant des espaces de noms de cette façon, même si possible, semble antithétique à la philosophie de conception actuelle.

2
bgulko

Simple - Ne faites pas cette merde.

Y compris un en-tête dans un espace de noms est une chose muette à faire et ne devrait pas être faite. Si vous n'êtes pas satisfait de la liste de noms d'un en-tête, puis placez-vous à l'auteur ou à la fourche. Y compris l'en-tête dans un espace de noms est totalement inefficace et inutile.

2
DeadMG