Espace de noms + fonctions versus méthodes statiques sur une classe

290

Disons que j'ai ou vais écrire un ensemble de fonctions connexes. Disons qu'ils sont liés aux mathématiques. Sur le plan organisationnel, dois-je:

  1. Écrire ces fonctions et les mettre dans mon MyMathespace de noms et y faire référence viaMyMath::XYZ()
  2. Créez une classe appelée MyMathet rendez ces méthodes statiques et reportez-vous de la même manièreMyMath::XYZ()

Pourquoi devrais-je choisir l'un plutôt que l'autre pour organiser mon logiciel?

RobertL
la source
4
D'une part, les espaces de noms sont des ajouts plus récents au langage, par rapport aux classes et aux méthodes statiques, qui étaient dans le langage à partir du moment où il était appelé "C avec les classes". Certains programmeurs peuvent être plus à l'aise avec les anciennes fonctionnalités. Certains autres programmeurs utilisent peut-être d'anciens compilateurs. Just my $ .02
Rom
21
@Rom: Vous avez raison sur les "anciens programmeurs", mais vous avez tort sur les "anciens compilateurs". Les espaces de noms sont correctement compilés depuis des éons (j'ai travaillé avec eux avec Visual C ++ 6, datant de 1998!). En ce qui concerne le "C avec les classes", certaines personnes de ce forum n'étaient même pas nées lorsque cela s'est produit: l'utiliser comme argument pour éviter une fonctionnalité C ++ standard et répandue est une erreur. En conclusion, seuls les compilateurs C ++ obsolètes ne prennent pas en charge les espaces de noms. N'utilisez pas cet argument comme excuse pour ne pas les utiliser.
paercebal
@paercebal: certains anciens compilateurs sont toujours utilisés dans le monde embarqué. La non-prise en charge des espaces de noms est probablement l'un des plus petits inconvénients que l'on doit supporter lors de l'écriture de code pour divers petits processeurs avec lesquels tout le monde interagit tous les jours: votre chaîne stéréo, votre micro-ondes, l'unité de commande du moteur dans votre voiture, le feu de circulation, etc. soyez clair: je ne préconise pas de ne pas utiliser de meilleurs compilateurs plus récents partout. Au conrare: je suis tout à fait pour les nouvelles fonctionnalités du langage (sauf RTTI;)). Je fais juste remarquer qu'une telle tendance existe
Rom
13
@Rom: Dans le cas actuel, l'auteur de la question a le choix, donc apparemment, aucun de ses compilateurs ne parvient à compiler un code à espace de noms. Et comme il s'agit d'une question sur C ++, une réponse C ++ doit être donnée, y compris en mentionnant les espaces de noms et les solutions RTTI au problème si nécessaire. Donner une réponse C ou une réponse C-avec-classes-pour-compilateurs obsolètes est hors sujet.
paercebal
2
"Just my $ .02" - cela vaudrait plus si vous aviez fourni des preuves de compilateurs C ++ existants qui ne prennent pas en charge les espaces de noms. "certains anciens compilateurs sont encore utilisés dans le monde embarqué" - c'est idiot; le «monde intégré» est un développement plus récent que les espaces de noms C ++. Le "C avec classes" a été introduit en 1979, bien avant que quiconque n'intègre du code C dans quoi que ce soit. "Je souligne simplement qu'une telle tendance existe" - même si c'est le cas, cela n'a rien à voir avec cette question.
Jim Balter

Réponses:

243

Par défaut, utilisez les fonctions d'espaces de noms.

Les classes doivent créer des objets et non remplacer des espaces de noms.

En code orienté objet

Scott Meyers a écrit un article entier pour son livre Effective C ++ sur ce sujet, "Préférez les fonctions non-membres non-amis aux fonctions membres". J'ai trouvé une référence en ligne à ce principe dans un article de Herb Sutter:http://www.gotw.ca/gotw/084.htm

La chose importante à savoir est la suivante: En C ++, les fonctions du même espace de noms qu'une classe appartiennent à l'interface de cette classe (car ADL recherchera ces fonctions lors de la résolution des appels de fonction).

Les fonctions à espace de nom, sauf si elles sont déclarées «ami», n'ont pas accès aux internes de la classe, contrairement aux méthodes statiques.

Cela signifie, par exemple, que lors de la maintenance de votre classe, si vous devez modifier les internes de votre classe, vous devrez rechercher des effets secondaires dans toutes ses méthodes, y compris les effets statiques.

Extension I

Ajout de code à l'interface d'une classe.

En C #, vous pouvez ajouter des méthodes à une classe même si vous n'y avez pas accès. Mais en C ++, c'est impossible.

Mais, toujours en C ++, vous pouvez toujours ajouter une fonction d'espacement de noms, même à une classe que quelqu'un a écrite pour vous.

Voyez de l'autre côté, cela est important lors de la conception de votre code, car en plaçant vos fonctions dans un espace de noms, vous autoriserez vos utilisateurs à augmenter / compléter l'interface de la classe.

Extension II

Un effet secondaire du point précédent, il est impossible de déclarer des méthodes statiques dans plusieurs en-têtes. Chaque méthode doit être déclarée dans la même classe.

Pour les espaces de noms, les fonctions du même espace de noms peuvent être déclarées dans plusieurs en-têtes (la fonction d'échange presque standard en est le meilleur exemple).

Extension III

La fraîcheur de base d'un espace de noms est que dans certains codes, vous pouvez éviter de le mentionner, si vous utilisez le mot-clé "using":

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Et vous pouvez même limiter la "pollution" à une seule classe:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Ce «modèle» est obligatoire pour la bonne utilisation de l'idiome d'échange presque standard.

Et cela est impossible à faire avec des méthodes statiques dans les classes.

Ainsi, les espaces de noms C ++ ont leur propre sémantique.

Mais cela va plus loin, car vous pouvez combiner des espaces de noms d'une manière similaire à l'héritage.

Par exemple, si vous avez un espace de noms A avec une fonction AAA, un espace de noms B avec une fonction BBB, vous pouvez déclarer un espace de noms C et amener AAA et BBB dans cet espace de noms avec le mot-clé using.

Conclusion

Les espaces de noms sont pour les espaces de noms. Les cours sont pour les cours.

C ++ a été conçu pour que chaque concept soit différent et soit utilisé différemment, dans différents cas, comme solution à différents problèmes.

N'utilisez pas de classes lorsque vous avez besoin d'espaces de noms.

Et dans votre cas, vous avez besoin d'espaces de noms.

paercebal
la source
Cette réponse peut-elle également s'appliquer aux threads, c'est-à-dire qu'il est préférable d'utiliser des espaces de noms plutôt que des méthodes statiques pour les threads?
dashesy
3
@dashesy: ​​les espaces de noms et les méthodes statiques n'ont rien à voir avec les threads, donc oui, les espaces de noms sont meilleurs car les espaces de noms sont presque toujours meilleurs que les méthodes statiques. Si une chose, les méthodes statiques ont accès aux variables des membres de classe, elles ont donc en quelque sorte une valeur d'encapsulation inférieure à celle des espaces de noms. Et l'isolement des données est encore plus important dans l'exécution filetée.
paercebal
@ paercebal- merci, j'utilisais les méthodes de classe statique pour les fonctions de thread. Maintenant, je comprends que j'utilisais mal la classe comme espace de noms, alors quelle est selon vous la meilleure approche pour avoir plusieurs threads dans un même objet? J'ai aussi posé cette question sur SO, j'apprécie si vous pouvez faire la lumière (ici ou dans la question elle-même)
dashesy
1
@dashesy: ​​Vous demandez des ennuis. Ce que vous voulez avec différents threads, c'est isoler les données qui ne sont pas censées être partagées, donc avoir plusieurs threads ayant un accès privilégié aux données privées d'une classe est une mauvaise idée. Je voudrais cacher un thread à l'intérieur d'une classe et m'assurer d'isoler les données de ce thread des données du thread principal. Bien sûr, les données qui sont censées être partagées peuvent alors être membres de cette classe, mais elles doivent toujours être synchronisées (verrous, atomiques, etc.). Je ne sais pas combien de bibliothèques vous avez accès, mais utiliser des tâches / async est encore mieux.
paercebal
la réponse de paercebal devrait être celle qui est acceptée! Juste un lien de plus pour le swap presque standard () via namespace + ADL -> stackoverflow.com/questions/6380862/…
Gob00st
54

Il y a beaucoup de gens qui ne seraient pas d'accord avec moi, mais voici comment je le vois:

Une classe est essentiellement une définition d'un certain type d'objet. Les méthodes statiques doivent définir des opérations intimement liées à cette définition d'objet.

Si vous allez simplement avoir un groupe de fonctions connexes non associées à un objet sous-jacent ou à la définition d'un type d'objet , alors je dirais aller avec un espace de noms uniquement. Juste pour moi, conceptuellement, c'est beaucoup plus sensé.

Par exemple, dans votre cas, demandez-vous: "Qu'est-ce qu'un MyMath?" Si MyMathne définit pas une sorte d'objet, alors je dirais: n'en faites pas une classe.

Mais comme je l'ai dit, je sais qu'il y a beaucoup de gens qui seraient (même avec véhémence) en désaccord avec moi à ce sujet (en particulier, les développeurs Java et C #).

Dan Tao
la source
3
Vous avez une perspective très pure à ce sujet. Mais en pratique, une classe avec des méthodes entièrement statiques peut être utile: vous pouvez typedefles utiliser, les utiliser comme paramètres de modèle, etc.
Shog9
56
C'est parce que les gens Jave et C # n'ont pas le choix.
Martin York
7
@ shog9. Vous pouvez également modéliser les fonctions!
Martin York
6
@Dan: vraisemblablement, qui nécessitait des routines mathématiques et voulait prendre en charge le "branchement" de différentes implémentations.
Shog9
1
@Dan: "Je pense que si quelqu'un souhaite utiliser une classe comme paramètre de modèle, cette classe définit presque sûrement un objet sous-jacent." Non pas du tout. Pensez aux traits. (Néanmoins, je suis entièrement d'accord avec votre réponse.)
sbi
18
  • Si vous avez besoin de données statiques, utilisez des méthodes statiques.
  • S'il s'agit de fonctions de modèle et que vous souhaitez pouvoir spécifier un ensemble de paramètres de modèle pour toutes les fonctions ensemble, utilisez des méthodes statiques dans une classe de modèle.

Sinon, utilisez des fonctions d'espaces de noms.


En réponse aux commentaires: oui, les méthodes statiques et les données statiques ont tendance à être sur-utilisées. C'est pourquoi je n'ai proposé que deux scénarios connexes où je pense qu'ils peuvent être utiles. Dans l'exemple spécifique de l'OP (un ensemble de routines mathématiques), s'il voulait pouvoir spécifier des paramètres - par exemple, un type de données de base et une précision de sortie - qui seraient appliqués à toutes les routines, il pourrait faire quelque chose comme:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Si vous n'avez pas besoin que, puis par tous les moyens d' utiliser un espace de noms.

Shog9
la source
2
les données dites statiques peuvent être des données de niveau espace de noms dans le fichier d'implémentation de l'espace de noms, ce qui réduit encore plus le couplage car il n'a pas à apparaître dans l'en-tête.
Motti
Les données statiques ne valent pas mieux que les globales de portée d'espace de noms.
coppro
@coppro. Ils sont au moins une étape dans la chaîne évolutive des globaux aléatoires car ils peuvent être rendus privés (mais autrement d'accord).
Martin York,
@Motti: OTOH, si vous le voulez dans l'en-tête (fonctions inline / template), vous êtes de retour à être laid à ce sujet.
Shog9
1
Exemple intéressant, utiliser une classe comme raccourci pour éviter de répéter des templatearguments!
underscore_d
13

Vous devez utiliser un espace de noms, car un espace de noms présente de nombreux avantages par rapport à une classe:

  • Vous n'avez pas à tout définir dans le même en-tête
  • Vous n'avez pas besoin d'exposer toute votre implémentation dans l'en-tête
  • Vous ne pouvez pas usingun membre de la classe; vous pouvez usingun membre d'espace de noms
  • Vous ne pouvez pas using class, mais ce using namespacen'est pas souvent une bonne idée
  • L'utilisation d'une classe implique qu'il y a un objet à créer alors qu'il n'y en a vraiment pas

Les membres statiques sont, à mon avis, très très surutilisés. Ils ne sont pas une réelle nécessité dans la plupart des cas. Les fonctions de membres statiques sont probablement mieux loties que les fonctions de portée de fichier, et les membres de données statiques ne sont que des objets globaux avec une meilleure réputation non méritée.

coppro
la source
3
"Vous n'avez pas besoin d'exposer toute votre implémentation dans l'en-tête" ni vous lorsque vous utilisez une classe.
Vanuan
Encore plus: si vous utilisez des espaces de noms, vous ne pouvez pas exposer toute votre implémentation dans l'en-tête (vous vous retrouverez avec plusieurs définitions de symboles). Les fonctions de membre de classe en ligne vous permettent de le faire.
Vanuan
1
@Vanuan: Vous pouvez exposer les implémentations d'espace de noms dans l'en-tête. Utilisez simplement le inlinemot - clé pour satisfaire ODR.
Thomas Eding
@ThomasEding n'a pas besoin! =
Can
3
@Vanuan: Une seule chose est garantie par le compilateur lors de l'utilisation inline, et ce n'est PAS "d'inliner" le corps d'une fonction. Le but réel (et garanti par la norme) du inlineest d'empêcher les définitions multiples. Lisez à propos de "One Definition Rule" pour C ++. En outre, la question SO liée n'était pas compilée en raison de problèmes d'en-tête précompilés au lieu de problèmes ODR.
Thomas Eding
3

Je préférerais les espaces de noms, de cette façon, vous pouvez avoir des données privées dans un espace de noms anonyme dans le fichier d'implémentation (il n'a donc pas du tout à apparaître dans l'en-tête par opposition aux privatemembres). Un autre avantage est que, par usingvotre espace de noms, les clients des méthodes peuvent refuser de spécifierMyMath::

Motti
la source
2
Vous pouvez également avoir des données privées dans un espace de noms anonyme dans le fichier d'implémentation avec des classes. Je ne suis pas sûr de suivre votre logique.
Patrick Johnmeyer
0

Une raison de plus pour utiliser la classe - Option pour utiliser des spécificateurs d'accès. Vous pouvez ensuite éventuellement diviser votre méthode statique publique en méthodes privées plus petites. La méthode publique peut appeler plusieurs méthodes privées.

Milind T
la source
6
Les modificateurs d'accès sont sympas, mais même la privateméthode most est plus accessible qu'une méthode dont le prototype n'est pas du tout publié dans un en-tête (et reste donc invisible). Je ne mentionne même pas la meilleure encapsulation offerte par les fonctions d'espaces de noms anonymes.
paercebal
2
Les méthodes privées sont, à l'OMI, inférieures à cacher la fonction elle-même dans l'implémentation (fichier cpp) et ne jamais l'exposer dans le fichier d'en-tête. Veuillez expliquer cela dans votre réponse et pourquoi vous préférez utiliser des membres privés . Jusque-là -1.
nonsensickle
@nonsensickle Peut-être qu'il veut dire qu'une fonction gigantesque avec de nombreuses sections répétées peut être décomposée en toute sécurité tout en cachant les sous-sections incriminées derrière privé, empêchant les autres d'y accéder si elles sont dangereuses / nécessitent une utilisation très prudente.
Troyseph
1
@Troyseph même ainsi, vous pouvez masquer ces informations dans un espace de noms sans nom dans le .cppfichier, ce qui les rendrait privées pour cette unité de traduction sans donner d'informations superflues à quiconque lisant le fichier d'en-tête. En fait, j'essaie de défendre l'idiome PIMPL.
nonsensickle
Vous ne pouvez pas le mettre dans un .cppfichier si vous souhaitez utiliser des modèles.
Yay295
0

L'espace de noms et la méthode de classe ont leurs utilisations. L'espace de noms peut être réparti sur plusieurs fichiers, mais c'est une faiblesse si vous devez appliquer tout le code associé pour aller dans un seul fichier. Comme mentionné ci-dessus, la classe vous permet également de créer des membres statiques privés dans la classe. Vous pouvez l'avoir dans l'espace de noms anonyme du fichier d'implémentation, mais il est toujours plus étendu que de les avoir dans la classe.

rocd
la source
"[stocker des choses] dans l'espace de noms anonyme du fichier d'implémentation [est] une portée plus grande que de les avoir dans la classe" - non, ce n'est pas le cas. dans les cas où un accès privilégié aux membres n'est pas requis, les éléments à espace de noms anonyme sont plus privés que private:ceux. et dans de nombreux cas où un accès privilégié semble être requis, cela peut être pris en compte. la fonction la plus «privée» est celle qui n'apparaît pas dans un en-tête. private:les méthodes ne peuvent jamais bénéficier de cet avantage.
underscore_d