Méthode correcte pour définir les méthodes d'espace de noms C ++ dans le fichier .cpp

108

Probablement un doublon, mais pas facile à rechercher ...

Étant donné un en-tête comme:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

J'ai vu method()défini de plusieurs manières dans le fichier .cpp:

Version 1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

Version 2:

using namespace ns1;

void MyClass::method()
{
 ...
}

Version 3:

void ns1::MyClass::method()
{
 ...
}

Y a-t-il une «bonne» façon de le faire? Est-ce que certains de ces éléments sont «faux» en ce sens qu'ils ne signifient pas tous la même chose?

Monsieur Boy
la source
Dans la plupart des codes, je vois généralement la troisième version (mais je ne sais pas pourquoi: D), la deuxième version est simplement opposée à la raison pour laquelle les espaces de noms sont introduits, je suppose.
Mr.Anubis
1
Comme dans un fait intéressant, les outils de refactoring de Visual Assist sont heureux de générer du code en utilisant le n ° 1 ou le n ° 3, selon le style déjà utilisé dans le fichier cible.
M. Boy

Réponses:

51

La version 2 n'est pas claire et pas facile à comprendre car vous ne savez pas à quel espace de noms MyClassappartient et c'est juste illogique (la fonction de classe n'est pas dans le même espace de noms?)

La version 1 a raison car elle montre que dans l'espace de noms, vous définissez la fonction.

La version 3 a raison également parce que vous avez utilisé l' ::opérateur de résolution d'étendue pour faire référence à MyClass::method ()dans l'espace de noms ns1. Je préfère la version 3.

Voir Espaces de noms (C ++) . C'est la meilleure façon de procéder.

GILGAMESH
la source
22
Appeler le n ° 2 «faux» est une énorme exagération. Selon cette logique, tous les noms de symboles sont «faux» car ils peuvent potentiellement masquer d'autres noms de symboles dans d'autres portées.
dix
C'est illogique. Il se compilera bien (désolé, mal expliqué cela dans la réponse), mais pourquoi définiriez-vous une fonction en dehors de son espace de noms? Cela déroute le lecteur. De plus, lorsque de nombreux espaces de noms sont utilisés, cela ne montre pas à quel espace de noms appartient MyClass. Les versions 1 et 3 résolvent ce problème. En conclusion, ce n'est pas faux, mais juste peu clair et déroutant.
GILGAMESH
3
Je suis d'accord avec @PhoenicaMacia, l' astuce d' utilisation est horrible et peut prêter à confusion. Considérez une classe qui implémente un opérateur en tant que fonction libre, dans l'en-tête que vous auriez namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }, maintenant dans le fichier cpp avec l' instruction using , vous pouvez définir la fonction membre comme void X::f() {}, mais si vous définissez X operator==(X const&, X const&)vous définirez un opérateur différent de celui défini dans l'en-tête (vous devrez utiliser 1 ou 3 pour la fonction libre).
David Rodríguez - dribeas
1
En particulier, je préfère 1, et l'exemple de l'article lié ne résout vraiment rien le premier exemple utilise 1, le second utilise un mélange de 1 et 3 (les fonctions sont définies avec qualification, mais elles sont définies dans l'espace de noms externe)
David Rodríguez - dribeas
1
Sur les 3, je dirais que 1) est le meilleur, mais la plupart des IDE ont l'habitude plutôt ennuyeuse de tout mettre en retrait dans cette déclaration d'espace de noms et cela ajoute une certaine confusion.
locka
28

5 ans plus tard et j'ai pensé que je mentionnerais ceci, qui à la fois est joli et n'est pas mal

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Puzomor Croatie
la source
3
C'est la meilleure réponse. Il a l'air le plus propre et évite les problèmes avec les versions 1 d'OP, qui peuvent amener des choses dans l'espace de noms involontairement, et 2, qui peuvent amener des choses dans l'espace global sans le vouloir.
ayane_m
Oui, c'est une excellente combinaison de moins de frappe que 3, tout en déclarant explicitement l'intention.
jb
14

J'utilise la version 4 (ci-dessous) car elle combine la plupart des avantages de la version 1 (lacune de la définition résoective) et de la version 3 (être au maximum explicite). Le principal inconvénient est que les gens n'y sont pas habitués, mais comme je le considère techniquement supérieur aux alternatives, cela ne me dérange pas.

Version 4: utilisez la qualification complète à l'aide d'alias d'espace de noms:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

Dans mon monde, j'utilise fréquemment des alias d'espace de noms car tout est explicitement qualifié - à moins que cela ne puisse pas (par exemple les noms de variables) ou qu'il s'agisse d'un point de personnalisation connu (par exemple swap () dans un modèle de fonction).

Dietmar Kühl
la source
1
Bien que je pense que la logique «c'est mieux, donc je m'en fiche si ça déroute les gens» est imparfaite, je dois admettre que c'est une bonne approche pour les espaces de noms imbriqués.
Mr. Boy
1
+1 pour l'excellente idée "pourquoi-n'ai-je-pas-pensé-à-ça"! (Quant à "les gens ne sont pas habitués aux [nouvelles choses techniquement supérieures]", ils s'y habitueront si plus de gens le font.)
wjl
Juste pour être sûr de bien comprendre, les deux outeret sont-ils déjà innerdéfinis comme des espaces de noms dans d'autres fichiers d'en-tête?
dekuShrub
4

La version 3 rend très explicite l'association entre la classe et l'espace de noms au détriment de plus de frappe. La version 1 évite cela mais capture l'association avec un bloc. La version 2 a tendance à cacher cela, donc j'éviterais celle-là.

Paul Joireman
la source
3

Je choisis Num.3 (aka la version verbeuse). C'est plus typé, mais l'intention est exacte pour vous et pour le compilateur. Le problème que vous avez publié tel quel est en fait plus simple que le monde réel. Dans le monde réel, il existe d'autres portées pour les définitions, pas seulement les membres de classe. Vos définitions ne sont pas très compliquées avec les classes uniquement - car leur portée n'est jamais rouverte (contrairement aux espaces de noms, à la portée globale, etc.).

Num.1 cela peut échouer avec des portées autres que des classes - tout ce qui peut être rouvert. Ainsi, vous pouvez déclarer une nouvelle fonction dans un espace de noms en utilisant cette approche, ou vos inlines pourraient finir par être remplacées via ODR. Vous en aurez besoin pour certaines définitions (notamment, les spécialisations de modèles).

Num.2 Ceci est très fragile, en particulier dans les grandes bases de code - à mesure que les en-têtes et les dépendances changent, votre programme échouera à se compiler.

Num.3 C'est idéal, mais beaucoup à taper - ce que votre intention est de définir quelque chose . C'est exactement cela, et le compilateur intervient pour s'assurer que vous n'avez pas fait d'erreur, qu'une définition n'est pas désynchronisée avec sa déclaration, etc.

Justin
la source
2

Tous les moyens sont bons et chacun a ses avantages et ses inconvénients.

Dans la version 1, vous avez l'avantage de ne pas avoir à écrire l'espace de noms devant chaque fonction. L'inconvénient est que vous obtiendrez une identification ennuyeuse, surtout si vous avez plus d'un niveau d'espaces de noms.

Dans la version 2, vous rendez votre code plus propre, mais si vous avez plus d'un espace de noms en cours d'implémentation dans le CPP, l'un peut accéder directement aux fonctions et variables de l'autre, rendant votre espace de noms inutile (pour ce fichier cpp).

Dans la version 3, vous devrez taper plus et vos lignes de fonctions peuvent être plus grandes que l'écran, ce qui est mauvais pour les effets de conception.

Il y a aussi une autre façon dont certaines personnes l'utilisent. Il est similaire à la première version, mais sans les problèmes d'identification.

C'est comme ça:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

A vous de choisir lequel est le meilleur pour chaque situation =]

Renan Greinert
la source
14
Je ne vois aucune raison d'utiliser une macro ici. Si vous ne voulez pas mettre en retrait, ne mettez pas en retrait. L'utilisation d'une macro rend simplement le code moins évident.
tenfour
1
Je pense que la dernière version que vous mentionnez est utile chaque fois que vous voulez compiler votre code avec d'anciens compilateurs qui ne prennent pas en charge les espaces de noms (Oui, certains dinosaures sont toujours là). Dans ce cas, vous pouvez placer la macro dans une #ifdefclause.
Luca Martini
Vous n'avez pas à vous identifier si vous ne le souhaitez pas, mais si vous n'utilisez pas de macros, certains IDE essaieront de le faire pour vous. Par exemple, dans Visual Studio, vous pouvez sélectionner le code entier et appuyer sur ALT + F8 pour l'identification automatique. Si vous n'utilisez pas les définitions, vous perdrez cette fonctionnalité. De plus, je ne pense pas que OPEN_ (namespace) et CLOSE_ (namespace) soient moins évidents, si vous l'avez dans votre norme de codage. La raison donnée par @LucaMartini est également intéressante.
Renan Greinert
Si cela a été rendu générique ie #define OPEN_NS(X)je pense que c'est un peu utile, mais pas vraiment ... Je ne m'oppose pas aux macros mais cela semble un peu OTT. Je pense que l'approche de Dietmar Kühl est meilleure pour les espaces de noms imbriqués.
M. Boy