Partage
  • Partager sur Facebook
  • Partager sur Twitter

Faut-il toujours indiquer les namespaces complets?

    24 septembre 2022 à 0:16:55

    Salut tout le monde ! :pirate:

    Est-ce que quelqu'un sait s'il est nécessaire (ou obligatoire) de toujours indiquer les namespaces complets sur tous les objets C++ dans un projet ? J'ai travaillé sur plusieurs projets en entreprise et dans certains d'entre eux il était obligatoire de toujours mettre les namespaces en entier. Dans d'autres projets en revanche il n'y avait aucune règle par rapport à cela.

    Aujourd'hui, j'essaie de mettre en place une compilation avec l'option Unity Build mais cela ne marche pas et donc j'ignore si cela est dû à "une mauvaise habitude de codage" qui serait de ne pas mettre les namespaces...

    • Partager sur Facebook
    • Partager sur Twitter
      24 septembre 2022 à 0:49:51

      Pour les "using" d'espaces de noms. La seule règle absolue est de toujours être explicite dans les fichiers qui se retrouvent inclus. Dans les autres, il y a moyen de s'en sortir car localement on peut maitriser les effets de bords qui ne se propageront jamais. Cf toutes les FAQ de C++, et les bons cours.

      L'unity build consiste à inclure TOUS les fichiers du projets, même ceux qui normalement ne sont jamais inclus (et servent juste de point de départ pour former les unités de traduction).

      Je crois que tout est dit au sujet "namespace & unity build", il ne reste qu'à conclure le 2 + 2. Ce n'est pas forcément la source du problème. P.ex. on pourrait encore parler des espaces de noms anonymes collés dans les fichiers d'en-tête...

      -
      Edité par lmghs 24 septembre 2022 à 0:50:17

      • Partager sur Facebook
      • Partager sur Twitter
      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
        24 septembre 2022 à 1:52:45

        Autechre a écrit:

        Aujourd'hui, j'essaie de mettre en place une compilation avec l'option Unity Build mais cela ne marche pas et donc j'ignore si cela est dû à "une mauvaise habitude de codage" qui serait de ne pas mettre les namespaces...


        Il peut y avoir plusieurs raisons qui font que la compilation séparée fonctionne mais pas le unity build:

        • collusion de namespaces entre fichiers cpp à cause de using intempestifs qui ont été écrit avec la compilation séparée comme acquise dans la tête des devs de l'époque.
        • collusion de fonctions statiques entre fichiers cpp (typiquement tout ce qui a un linkage interne et qui fonctionnait bien en compilation séparée peut exploser dans un unity build).
        • trop de code qui se retrouve dans la même unité de compilation du unity build => dans ce cas il faut spliter en unités de compilations plus petites (attention à ne pas mettre des fichiers cpp identiques dans plusieurs groupes, sinon violation ODR).
        • d'autres cas tordus auxquels je ne pense pas.

        -
        Edité par SpaceIn 24 septembre 2022 à 1:55:04

        • Partager sur Facebook
        • Partager sur Twitter
          24 septembre 2022 à 15:37:33

          Salut,

          Peut être faut il commencer par comprendre ce qu'est réellement un espace de noms.

          Faisons simple : c'est une boite dans laquelle on range différentes fonctionnalités "qui vont bien ensembles".  Par exemple, l'espace de noms std regroupe toutes les fonctionnalités dont on estime que la bibliothèque standard devrait nous fournir (std étant les trois lettres que l'on retrouve dans STandarD ).

          Ensuite, il faudra comprendre ce qui se passe lorsque l'on utilise la directive using namespace <n'importe quel nom>.

          C'est toujours aussi simple car cela revient à ... renverser la boite sur la table.

          Le principal problème, c'est que ces boites permettent justement de faire la différence entre deux fonctionnalités qui portent le même nom, mais qui se trouvent, justement, dans des boites différentes.

          Si bien que, si "par malheur" tu décide de renverser les deux boites qui contiennent ces deux fonctionnalités portant le même nom sur la même table, ben, tu devra quand même utiliser l'espace de noms pour les différencier.

          A partir de là, ben, il y a quelques subtilités, car on n'est pas forcément obligé de renverser toutes la boite sur la table.  On peut aussi se "contenter" de ne poser qu'un élément ou deux, par exemple, avec la directive using std::cout;

          De plus, il faut voir la "portée" du "contexte" dans lequel tu renverse la boite sur la table. Ou, si tu préfère, la "portion de code" dans laquelle la table sur laquelle tu as renversé les différentes boites pourra être utilisée.

          A ce sujet, il y a (en gros) trois possibilités:

          Tu le fais au niveau d'une fonction:

          #include <iostream>
          #include <string>
          #include <vector>
          std::vector<std::string> foo() {
          	using namespace std;
          	vector<string> tab;
          	string str{ "hello" };
          	str.append(1, ' ')
          		.append("crual world");
          	tab.push_back(str);
          	return tab;
          }
          int main() {
          	using std::cout;
          	auto tab = foo();
          	cout <<tab[0];
          
          }

          Même si on est -- comme moi -- "par principe" contre l'idée de travailler de la sorte, il faut bien avouer que le contexte est suffisamment réduit que pour ne gêner personne et que ce code ne présente donc absolument aucun problème

          Vient un contexte "un peu moins favorable" qui consiste à n'utiliser cette directive que dans les fichiers d'implémentation (les fichier *.cpp)

          Test.hpp : pas de directive using namespace

          #pragma once
          #include <string>
          #include <iosfwd>
          struct Person{
              std::string name;
              std::string firstName;
              Address addres;
              Date birthdate;
          };
          std::osteam& operator<< (std::ostream &, Person const &);

          fichier d'implémentation avec directive using namespace globale à ce fichier

          #include <iostream>
          using namespace std;
          int main() {
              Person p;
              /* ... */
              cout << p;
          
          }
          
          osteam& operator<< (ostream& out, Person const&) {
              out << p.name << "\n"
                  << p.firstName << "\n"
                  << p.address << "\n"
                  << p.birthDate << "\n";
              return out;
          }

          Autant le dire tout de suite, je suis déjà beaucoup moins disposé à accepter ce code, pour la simple et bonne raison que cela augmente largement le risque de confusion, en fonction des fichiers qui seront inclus dans l'histoire.

          Cependant, j'arriverai encore -- contraint et forcé -- à ne pas faire une maladie face à ce code car les risques liés à l'utilisation de la directive restent "confinés" dans une portée clairement maîtrisée : elle ne va pas s'étendre à des fichiers d'implémentations sur laquelle je n'ai absolument aucun regard.

          Enfin, il y a le contexte tout à fait catastrophique d'une directive placée dans un fichier d'en-tête, par exemple

          Personne.hpp

          #pragma once
          #include <string>
          #include <iosfwd>
          using namespace std;
          struct Person{
              string name;
              string firstName;
              Address addres;
              Date birthdate;
          };
          osteam& operator<< (ostream &, Person const &);

          Et je sais que je ne pourrai pas m'empêcher de gueuler, même en y mettant la meilleure volonté du monde pour la simple et bonne raison que je n'ai absolument aucun contrôle sur la "portée finale" de cette directive.

          En effet, le but de ce fichier d'en-tête est de fournir une notion appelée Person à ... tout développeur qui pourrait en avoir besoin. Il est donc "logique" que ce fichier d'en-tête soit ... inclut "quelque part", et, souvent, à des endroits sur lesquels je n'aurai sans doute pas le moindre droit de regard.

          De plus, il y a le problème des inclusions en cascade: si un fichier d'implémentation inclut un fichier A.hpp, qui inclut lui-même un fichier B.hpp, qui inclut lui-même un fichier C.hpp, qui inclut mon fichire Person.hpp, hé bien cette directive va être active dans C.hpp, dans B.hpp, dans A.hpp et dans le fichier d'implémentation.

          En d'autres termes, nous pouvons dire que l'effet de cette directive devient totalement incontrôlable, et ca, c'est quelque chose que je ne veux ni ne peux me permettre.

          Enfin, il y a le problème des espaces de noms à rallonge. Une des raisons pour lesquelles nous y sommes confrontés tient souvent au fait qu'on a défini un espace de noms dans un espace de noms qui était lui-même défini ... dans un espace de noms.

          La bibliothèque standard commence à utiliser la technique, par exemple, avec l'espace de noms std::chrono.  Par contre, le framework boos est déjà coutumier du fait depuis de nombreuses années...

          Et force est d'avouer que l'utilisation systématique de std::literals, de std::chrono ou de boost::filesystem rend le code vachement plus lourd à lire sans forcément apporter grand chose.

          Par contre, cela ne justifie à mon sens pas d'utiliser la directive using namespace, et surtout pas dans un contexte aussi global que celui fourni par un fichier d'en-tête.  Entre autres, parce qu'il existe des alternatives, dont, principalement, la création d'alias, par exemple

          namespace sliterals = std::literal;

          ou, pourquoi pas, la création d'un alias de type (éventuellement dans un espace de noms "bien à soi"):

          #include <vector>
          #include <string>
          namespace MyNamespace{
              
              using StringVect = std::vector<std::string>;
          }

          Bref, tout le monde l'aura compris: s'il ne tenait qu'à moi, je bannirais purement l'usage de la directive using namespace <un nom quelconque>.

          Mais comme cela ne tient pas qu'à moi, le meilleur conseil à donner est très certainement de le faire dans un contexte aussi restreint et "bien maîtrisé" que possible, de manière à ce que la directive ne se retrouve pas à interférer avec des élément non contrôlés.

          -
          Edité par koala01 24 septembre 2022 à 17:55:22

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

          Faut-il toujours indiquer les namespaces complets?

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown