Les classes d'utilitaires avec uniquement des membres statiques sont-elles un anti-modèle en C ++?

39

La question Où devrais-je placer des fonctions qui ne sont pas liées à une classe a suscité un débat quant à savoir s'il est logique en C ++ de combiner des fonctions utilitaires dans une classe ou de simplement les faire exister en tant que fonctions libres dans un espace de noms.

Je viens d’un fond C # où cette dernière option n’existe pas et qui tend donc naturellement à utiliser des classes statiques dans le petit code C ++ que j’écris. La réponse la plus votée sur cette question ainsi que plusieurs commentaires indiquent toutefois que les fonctions libres doivent être préférées, suggérant même que les classes statiques étaient un anti-modèle. Pourquoi est-ce le cas en C ++? Au moins à la surface, les méthodes statiques sur une classe semblent impossible à distinguer des fonctions libres dans un espace de noms. Pourquoi donc la préférence pour ce dernier?

Les choses seraient-elles différentes si la collection de fonctions utilitaires nécessitait des données partagées, par exemple un cache que l'on pourrait stocker dans un champ statique privé?

PersonalNexus
la source
Cela ressemble un peu à l'antipattern de "décomposition fonctionnelle".
user281377
7
Réponse courte: Vous n'avez simplement pas besoin d'une classe pour envelopper ces fonctions. Les fonctions gratuites sont beaucoup plus propres à votre tâche que de les regrouper dans une construction pseudo-OO, solution de contournement dont vous n'avez besoin que dans les langages "purement OO".
Chris dit Réintégrer Monica

Réponses:

39

Je suppose que pour répondre à cela, nous devrions comparer les intentions des classes et des espaces de noms. Selon Wikipedia:

Classe

Dans la programmation orientée objet, une classe est une construction utilisée comme modèle pour créer des instances d'elle-même, appelées instances de classe, objets de classe, objets d'instance ou simplement des objets. Une classe définit les membres constitutifs qui permettent à ces instances de classe d'avoir un état et un comportement. Les membres de champs de données (variables membres ou variables d'instance) permettent à un objet de classe de conserver son état. D'autres types de membres, en particulier des méthodes, activent le comportement d'un objet de classe. Les instances de classe sont du type de la classe associée.

Espace de noms

En général, un espace de noms est un conteneur qui fournit un contexte aux identifiants qu'il contient (noms ou termes techniques) et permet de désactiver l'homonymie des identificateurs d'homonymes résidant dans différents espaces de noms.

Maintenant, qu'est-ce que vous essayez d'atteindre en plaçant les fonctions dans une classe (statique) ou un espace de noms? Je parierais que la définition d'un espace de noms décrit mieux votre intention - tout ce que vous voulez, c'est un conteneur pour vos fonctions. Vous n'avez besoin d'aucune des fonctionnalités décrites dans la définition de classe. Notez que les premiers mots de la définition de classe sont " Dans la programmation orientée objet ", pourtant il n'y a rien orienté objet dans une collection de fonctions.

Il y a probablement aussi des raisons techniques, mais en tant que personne venant de Java et essayant de comprendre le langage multi-paradigme qu'est C ++, la réponse la plus évidente est la suivante: parce que nous n'avons pas besoin de OO pour y parvenir.

Gyan aka Gary Buyn
la source
1
+1 pour "on n'a pas besoin de OO pour y arriver"
Ixrec
Je suis d’accord avec cela en tant que non-fan de OO, mais je suppose qu’il existe quelques rares situations où l’utilisation d’une classe All-static est la seule solution, par exemple, remplacer un espace de noms, car vous ne pouvez pas déclarer d’espace de noms imbriqué. une classe.
Wael Boutglay
26

Je serais très prudent en appelant cela un anti-modèle. Les espaces de noms sont généralement préférés, mais comme il n’existe aucun modèle d’espace de noms et que ceux-ci ne peuvent pas être passés en tant que paramètres de modèle, il est assez courant d’utiliser des classes ne comportant que des membres statiques.

Programmateur
la source
3
Les autres réponses ont passé au-dessus de la dernière ligne du PO. Les espaces de noms sont des étendues assez nommées, donc si vous avez besoin d'un contrôle d'accès, les classes sont le seul outil disponible.
cmannett85
2
@ cbamber85 pour être juste, je n'avais mis cette ligne qu'après avoir lu les deux premières réponses.
PersonalNexus
@PersonalNexus Ah juste, je n'ai pas réalisé!
cmannett85
10
@ cbamber85: Ce n'est pas tout à fait vrai. Vous obtiendrez une encapsulation encore meilleure en plaçant des fonctions "privées" dans le fichier d'implémentation, afin que les utilisateurs ne puissent même pas voir la déclaration privée.
UncleBens
2
Les modèles +1 sont en effet un point où les espaces de noms manquent encore de fonctionnalités.
Chris dit Réintégrer Monica
13

À l'époque, j'avais besoin de suivre un cours de FORTRAN. Je connaissais d’autres langues impératives à ce moment-là, alors j’ai pensé que je pouvais faire du Fortran sans trop étudier. Lorsque j'ai rendu mes premiers devoirs, le professeur me les a retournés et m'a demandé de les refaire: il a dit que les programmes en Pascal écrits en syntaxe FORTRAN ne comptent pas comme des soumissions valables.

Un problème similaire est en jeu ici: utiliser des classes statiques pour héberger des fonctions utilitaires en C ++ est un idiome étranger à C ++.

Comme vous l'avez mentionné dans votre question, l'utilisation de classes statiques pour les fonctions utilitaires en C # est une nécessité: les fonctions autonomes ne sont tout simplement pas une option en C #. Le langage nécessaire pour développer un modèle permettant aux programmeurs de définir des fonctions indépendantes d'une autre manière, notamment dans les classes d'utilitaires statiques. C'était une astuce Java prise mot à mot: par exemple, java.lang.Math et System.Math de .NET sont presque isomorphes.

C ++, cependant, offre des espaces de noms, une installation différente pour atteindre le même objectif, et l’utilise activement dans l’implémentation de sa bibliothèque standard. L'ajout d'une couche supplémentaire de classes statiques est non seulement inutile, mais également quelque peu contre-intuitif pour les lecteurs sans arrière-plan C # ou Java. En un sens, vous introduisez une "traduction de prêt" dans la langue pour quelque chose qui peut être exprimé de manière native.

Lorsque vos fonctions doivent partager des données, la situation est différente. Comme vos fonctions ne sont plus sans lien , le modèle Singleton devient le moyen privilégié pour répondre à cette exigence.

dasblinkenlight
la source
6
+1, sauf pour recommander des singletons. Ils ne sont pas préférés en C ++! Les fonctions peuvent être dans une classe si elles ont besoin de partager un état, mais les classes ne doivent pas être des singletons à moins qu'elles ne le soient vraiment, vraiment . Et ce n'est généralement pas le cas.
Maxpm
@Maxpm Ne vous méprenez pas, le singleton n'est préféré qu'aux variables statiques globales. Autre que cela, il n'y a rien "préféré" à ce sujet.
dasblinkenlight
2
Il y a ce bon article, Singletons: Résoudre des problèmes que vous ne saviez pas que vous n'avez jamais rencontrés depuis 1995 . En résumé, il est indiqué que coupler l'instance bien connue à la classe elle-même limite inutilement votre flexibilité et ne vous donne rien. La plupart du temps, il suffit d'avoir une classe et une instance partagée quelque part, mais ne le faites pas singleton.
Jan Hudec
Je ne suis pas sûr que "idiome étranger" soit le bon terme. C'est un idiome très commun. Je pense que de nombreuses équipes de programmation utilisent e2 de Stroustrup ...
Nick Keighley
10

Peut-être devriez-vous vous demander pourquoi vous souhaitez une classe entièrement statique?

La seule raison pour laquelle je peux penser est que d'autres langages (Java et C #) qui sont vraiment «tout est une classe» en ont besoin. Ces langages ne peuvent pas du tout créer de fonctions de haut niveau. Ils ont donc inventé une astuce pour les conserver: il s'agissait de la fonction membre static. C'est un peu une solution de contournement, mais le C ++ n'en a pas besoin, vous pouvez créer directement de toutes nouvelles fonctions indépendantes.

Si vous avez besoin de fonctions qui fonctionnent sur un élément de données spécifique, il est judicieux de les regrouper dans une classe contenant les données, mais ces fonctions cessent d'être des fonctions et commencent à être membres de cette classe qui opère sur les données de la classe.

Si vous avez une fonction qui n’opère pas sur un type de données particulier (j’utilise le mot ici car les classes sont un moyen de définir de nouveaux types de données), c’est vraiment un anti-motif qui les place dans une classe, pour rien d’autre que fins sémantiques.

gbjbaanb
la source
10
En effet, je dirais que la "classe avec tous les membres statiques" en Java est en fait un hack pour contourner une limitation de Java.
Charles Salvia
@CharlesSalvia: J'étais poli :) J'ai le même sentiment négatif que main () fasse également partie d'une classe java, bien que je comprenne pourquoi ils l'ont fait.
gbjbaanb
Cela semble en fait fournir la réponse à la raison pour laquelle vous voudriez une classe entièrement statique. Ou est-ce que je vous comprends mal? Par exemple, je dois conserver une variable dont la valeur peut changer, qui est lue par une classe (que je compte stocker dans la ROM) et écrite par une autre classe (qui sera dans la RAM). Il ne suffit pas de placer le sur un emplacement connu du ram. Le placer dans une classe (et ses accessoires) me permet d'affiner le contrôle d'accès. À peu près ce que vous décrivez dans votre deuxième paragraphe.
iheanyi
5

Au moins à la surface, les méthodes statiques sur une classe semblent impossible à distinguer des fonctions libres dans un espace de noms.

En d'autres termes, avoir une classe au lieu d'un espace de noms n'a aucun avantage.

Pourquoi donc la préférence pour ce dernier?

D'une part, cela vous évite de taper statictout le temps, bien qu'il s'agisse sans doute d'un avantage plutôt mineur.

Le principal avantage est que c'est l'outil le moins puissant pour le travail. Les classes peuvent être utilisées pour créer des objets, elles peuvent être utilisées comme type de variables ou comme arguments de modèle. Aucune de ces fonctionnalités ne vous intéresse pour votre collection de fonctions. Il est donc préférable d'utiliser un outil qui ne possède pas ces fonctionnalités, afin que la classe ne puisse pas être utilisée de manière accidentelle.

Suite à cela, l'utilisation d'un espace de noms indique immédiatement à tous les utilisateurs de votre code qu'il s'agit d'un ensemble de fonctions et non d'un plan détaillé à partir duquel créer des objets.

sepp2k
la source
5

... plusieurs commentaires disent cependant que les fonctions libres sont à préférer, suggérant même que les classes statiques étaient un anti-motif. Pourquoi est-ce le cas en C ++? Au moins à la surface, les méthodes statiques sur une classe semblent impossible à distinguer des fonctions libres dans un espace de noms. Pourquoi donc la préférence pour ce dernier?

Une classe entièrement statique fera le travail, mais c'est comme conduire un camion semi-remorque de 53 pieds jusqu'à l'épicerie pour des frites et de la salsa quand une berline à quatre portes fera l'affaire (c'est-à-dire qu'elle est excessive). Les cours viennent avec une petite quantité de frais généraux supplémentaires et leur existence peut donner à quelqu'un l'impression qu'instancier un peut être une bonne idée. Les fonctions libres offertes par C ++ (et C, où toutes les fonctions sont gratuites) ne le font pas; ce ne sont que des fonctions et rien d'autre.

Les choses seraient-elles différentes si la collection de fonctions utilitaires nécessitait des données partagées, par exemple un cache que l'on pourrait stocker dans un champ statique privé?

Pas vraiment. L'objectif initial de staticC (et plus tard C ++) était de proposer un stockage persistant utilisable à n'importe quel niveau de portée:

int counter() {
    static int value = 0;
    value++;
}

Vous pouvez placer l'étendue au niveau d'un fichier, ce qui la rend visible pour toutes les étendues plus étroites, mais pas en dehors du fichier:

static int value = 0;

int up() { value++; }
int down() { value--; }

Un membre de classe statique privé en C ++ a le même objectif mais est limité à la portée d'une classe. La technique utilisée dans l' counter()exemple ci-dessus fonctionne également dans les méthodes C ++ et est quelque chose que je vous conseillerais de faire si la variable n'a pas besoin d'être visible par toute la classe.

Blrfl
la source
Je suppose qu'un groupe de fonctions utilitaires non liées qui ne partagent aucune donnée devrait être mieux groupé dans un espace de noms. Mais si vous avez un groupe de fonctions utilitaires étroitement liées qui nécessitent un accès à des données partagées et peut-être un certain contrôle, une classe statique est le meilleur choix. Une statique dans une fonction n'est pas réentrante. Utiliser un module global ... légèrement mieux dans ce contexte, mais je pense toujours qu'une classe entièrement statique est la meilleure solution si vous avez les exigences que j'ai décrites.
iheanyi
S'ils partagent des données, pourraient-ils être meilleurs en tant que classe sans méthodes statiques?
Nick Keighley
@ NickKeighley Cela dépend de qui gagne le débat sur la question de savoir s'il est préférable de créer une instance et de la transmettre à tout ce qui en a besoin ou de simplement la rendre statique.
Blrfl
1

Si une fonction ne maintient aucun état et est réentrante, il semble inutile de la placer dans une classe (sauf si forcé par la langue). Si la fonction conserve un certain état (par exemple, elle ne peut être rendue thread-safe qu’à l’aide d’un mutex statique), les méthodes statiques sur une classe semblent alors appropriées.

Martin James
la source
1

Une grande partie du discours sur le sujet fait ici sens, mais il y a quelque chose de très fondamental au sujet de C ++ qui fait namespaceset classes / structs très différent.

Les classes statiques (les classes dont tous les membres sont statiques et où la classe ne sera jamais instanciée) sont elles-mêmes des objets. Ils ne sont pas simplement une namespacepour contenir des fonctions.

La méta-programmation de modèles nous permet d'utiliser une classe statique en tant qu'objet à la compilation.

Considère ceci:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Pour l'utiliser, nous avons besoin de fonctions contenues dans une classe. Un espace de noms ne fonctionnera pas ici. Considérer:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Maintenant pour l'utiliser:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Tant en tant que nouveau allocateur expose une allocateet releasefonction compatible, le passage à une nouvelle allocateur est facile.

Cela ne peut pas être réalisé avec des espaces de noms.

Avez-vous toujours besoin de fonctions pour faire partie d’une classe? Non.

L'utilisation d'une classe statique est-elle un anti-motif? Ça dépend du contexte.

Les choses seraient-elles différentes si la collection de fonctions utilitaires nécessitait des données partagées, par exemple un cache que l'on pourrait stocker dans un champ statique privé?

Dans ce cas, ce que vous essayez d'atteindre sera probablement mieux servi par la programmation orientée objet.

Michael Gazonda
la source
L'utilisation du mot clé inline est redondante ici car les méthodes définies dans une définition de classe sont implicitement inline. Voir en.cppreference.com/w/cpp/language/inline .
Trevor le
1

Comme John Carmack l'a dit:

"Parfois, l'implémentation élégante n'est qu'une fonction. Ce n'est pas une méthode. Ce n'est pas une classe. Ce n'est pas un framework. C'est juste une fonction."

Je pense que cela résume à peu près tout. Pourquoi voudriez-vous en faire une classe avec force si ce n'est clairement pas une classe? C ++ a le luxe que vous pouvez réellement utiliser des fonctions; en Java, tout est une méthode. Ensuite, vous avez besoin de nombreuses classes d’utilitaires.

juhist
la source