web-dev-qa-db-fra.com

Les classes utilitaires ne contenant que des membres statiques sont-elles un anti-modèle en C ++?

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

Je viens d'un arrière-plan C # où cette dernière option n'existe pas et donc naturellement tendance à 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 cependant que les fonctions libres doivent être préférées, suggérant même que les classes statiques étaient un anti-modèle. Pourquoi en est-il ainsi en C++? Au moins en surface, les méthodes statiques d'une classe semblent indiscernables des fonctions libres dans un espace de noms. Pourquoi donc la préférence pour ces derniers?

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

42
PersonalNexus

Je suppose que pour répondre que 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 qui est 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 objets. Une classe définit les membres constituants qui permettent à ces instances de classe d'avoir un état et un comportement. Les membres du champ de données (variables membres ou variables d'instance) permettent à un objet classe de conserver son état. D'autres types de membres, en particulier les méthodes, permettent 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 pour les identificateurs (noms, ou termes techniques ou mots) qu'il contient, et permet la désambiguïsation des identificateurs d'homonymes résidant dans différents espaces de noms.

Maintenant, qu'essayez-vous de réaliser en plaçant les fonctions dans une classe (statiquement) 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", mais il n'y a rien orienté objet dans une collection de fonctions.

Il y a probablement aussi des raisons techniques mais comme quelqu'un venant de Java et essayant de me familiariser avec le langage multi-paradigme qu'est le C++, la réponse la plus évidente pour moi est: parce que nous ne ' t besoin de OO pour y parvenir.

43
Gyan aka Gary Buyn

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'y a pas de modèles d'espace de noms et que les espaces de noms ne peuvent pas être passés en tant que paramètres de modèle, l'utilisation de classes ne contenant que des membres statiques est assez courante.

27
AProgrammer

À l'époque, j'avais besoin de prendre un cours de FORTRAN. Je connaissais alors d'autres langues impératives, alors j'ai pensé que je pouvais faire FORTRAN sans beaucoup étudier. Lorsque j'ai rendu mes premiers devoirs, le professeur me l'a rendu et a demandé de le refaire: il a dit que les programmes Pascal écrits en syntaxe FORTRAN ne comptent pas comme des soumissions valides.

Un problème similaire est en jeu ici: l'utilisation de 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 question de nécessité: les fonctions autonomes ne sont tout simplement pas une option en C #. Le langage devait développer un modèle permettant aux programmeurs de définir les fonctions autonomes d'une autre manière - à savoir, au sein des classes d'utilité statiques. C'était une Java prise mot pour mot: par exemple, Java.lang.Math et System.Math de .NET sont presque isomorphes.

Le C++, cependant, offre des espaces de noms, une fonctionnalité différente pour atteindre le même objectif, et il l'utilise activement dans la mise en œuvre de sa bibliothèque standard. L'ajout d'une couche supplémentaire de classes statiques est non seulement inutile, mais aussi quelque peu contre-intuitif pour les lecteurs sans C # ou Java background. Dans un sens, vous introduisez une "traduction de prêt" dans la langue pour quelque chose cela peut être exprimé nativement.

Lorsque vos fonctions doivent partager des données, la situation est différente. Parce que vos fonctions ne sont plus indépendantes , modèle Singleton devient le moyen préféré de répondre à cette exigence.

14
dasblinkenlight

Peut-être devez-vous vous demander pourquoi vous voudriez une classe entièrement statique?

La seule raison pour laquelle je peux penser est que d'autres langages (Java et C #) qui sont tout à fait "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, et c'était la fonction membre statique. Ils sont un peu une solution de contournement, mais C++ n'a pas besoin d'une telle chose, vous pouvez créer directement de toutes nouvelles fonctions indépendantes de haut niveau.

Si vous avez besoin de fonctions qui opèrent sur un élément de données spécifique, il est logique de les regrouper dans une classe qui contient les données, mais alors, ces fonctions cessent d'être des membres de cette classe qui opèrent sur les données de la classe.

Si vous avez une fonction qui ne fonctionne pas sur un type de données particulier (j'utilise le mot ici car les classes sont des moyens de définir de nouveaux types de données), alors c'est vraiment un anti-modèle pour les pousser dans une classe, pour rien d'autre que fins sémantiques.

10
gbjbaanb

Au moins en surface, les méthodes statiques d'une classe semblent indiscernables 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 ces derniers?

D'une part, cela vous évite de taper static tout le temps, bien que ce soit sans doute 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 n'est souhaitée pour votre collection de fonctions. Il est donc préférable d'utiliser un outil qui n'a pas ces fonctionnalités, afin que la classe ne puisse pas être accidentellement mal utilisée.

À la suite de cela, l'utilisation d'un espace de noms indique également immédiatement aux utilisateurs de votre code qu'il s'agit d'une collection de fonctions et non d'un modèle à partir duquel créer des objets.

5
sepp2k

... plusieurs commentaires disent cependant que les fonctions libres doivent être préférées, suggérant même que les classes statiques étaient un anti-modèle. Pourquoi en est-il ainsi en C++? Au moins en surface, les méthodes statiques d'une classe semblent indiscernables des fonctions libres dans un espace de noms. Pourquoi donc la préférence pour ces derniers?

Une classe entièrement statique fera le travail, mais c'est comme conduire un semi-camion de 53 pieds à l'épicerie pour des frites et de la salsa quand une berline à quatre portes fera l'affaire (c.-à-d., C'est exagéré). Les cours sont livrés avec une petite quantité de frais généraux supplémentaires et leur existence pourrait donner à quelqu'un l'impression qu'une instanciation pourrait être une bonne idée. Les fonctions gratuites 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 avait besoin de données partagées, par exemple un cache que l'on pourrait stocker dans un champ statique privé?

Pas vraiment. Le but initial de static en C (et plus tard C++) était d'offrir un stockage persistant utilisable à n'importe quel niveau de portée:

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

Vous pouvez étendre la portée 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++ sert le même objectif mais est limité à la portée d'une classe. La technique utilisée dans l'exemple counter() ci-dessus fonctionne également à l'intérieur des méthodes C++ et c'est quelque chose que je recommanderais réellement de faire si la variable n'a pas besoin d'être visible pour toute la classe.

5
Blrfl

Si une fonction ne maintient aucun état et est réentrante, il ne semble pas très utile de la placer à l'intérieur d'une classe (sauf si le langage le force). Si la fonction conserve un certain état (par exemple, elle ne peut être rendue thread-safe qu'au moyen d'un mutex statique), alors les méthodes statiques sur une classe semblent appropriées.

1
Martin James

Une grande partie du discours sur le sujet ici a du sens, bien qu'il y ait quelque chose de très fondamental à propos de C++ qui rend namespaces et classes/structs très différents.

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

La méta-programmation de modèle nous permet d'utiliser une classe statique comme objet au moment de 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 qu'un nouvel allocateur expose une fonction allocate et release qui est compatible, le passage à un nouvel allocateur est facile.

Ceci 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-modèle? Ça dépend du contexte.

Les choses seraient-elles différentes si la collection de fonctions utilitaires avait besoin de 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 une programmation orientée objet.

1
Michael Gazonda

Comme l'a dit John Carmack:

"Parfois, l'implémentation élégante est juste une fonction. Pas une méthode. Pas une classe. Pas un cadre. Juste une fonction."

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

1
juhist