41 votes

Utilisation à grande échelle du conseil de Meyer de préférer les fonctions de non-membre, non-ami ?

Depuis un certain temps, je conçois les interfaces de mes classes de manière à ce qu'elles soient minimales, en préférant les fonctions non membres enveloppées dans l'espace de noms aux fonctions membres. Je suis essentiellement les conseils de Scott Meyer dans l'article Comment les fonctions non membres améliorent l'encapsulation .

Je l'ai fait avec succès dans quelques projets à petite échelle, mais je me demande si cela fonctionne bien à plus grande échelle. Existe-t-il des projets C++ open-source de grande envergure, bien considérés, que je pourrais consulter et auxquels je pourrais peut-être faire référence et dans lesquels ce conseil est fortement suivi ?

Mise à jour : Merci pour tous les commentaires, mais ce n'est pas tant l'opinion qui m'intéresse que le fait de savoir si cela fonctionne bien dans la pratique à plus grande échelle. La réponse de Nick est la plus proche à cet égard, mais j'aimerais pouvoir voir le code. Toute description détaillée des expériences pratiques (positives, négatives, considérations pratiques, etc) serait également acceptable.

11voto

Nick Points 5293

C'est ce que je fais assez souvent dans le cadre du projet sur lequel je travaille ; le plus grand projet de mon entreprise actuelle compte environ 2 millions de lignes, mais il n'est pas open source, et je ne peux donc pas le fournir comme référence. Cependant, je dirai que je suis d'accord avec les conseils, d'une manière générale. Plus vous pouvez séparer la fonctionnalité qui n'est pas strictement contenue dans un seul objet de cet objet, meilleure sera votre conception.

Prenons l'exemple classique du polymorphisme : une classe de base Shape avec des sous-classes et une fonction virtuelle Draw(). Dans le monde réel, Draw() devrait prendre en compte un certain contexte de dessin, et potentiellement être consciente de l'état des autres objets dessinés, ou de l'application en général. Une fois que vous avez mis tout cela dans chaque implémentation de sous-classe de Draw(), il est probable qu'il y ait des chevauchements de code, ou que la majeure partie de la logique de Draw() se trouve dans la classe de base, ou quelque part ailleurs. Ensuite, si vous voulez réutiliser une partie de ce code, vous devrez fournir plus de points d'entrée dans l'interface, et éventuellement polluer les fonctions avec d'autres codes qui ne sont pas liés au dessin de formes (par exemple : la logique de corrélation du dessin de formes multiples). D'ici peu, ce sera le bordel, et vous souhaiterez avoir une fonction de dessin qui prenne une forme (et un contexte, et d'autres données) à la place, et que la forme n'ait que des fonctions/données entièrement encapsulées et n'utilisant pas ou ne référençant pas d'objets externes.

Quoi qu'il en soit, c'est mon expérience/conseil, pour ce qu'il vaut.

9voto

James McNellis Points 193607

Je dirais que l'avantage des fonctions non membres augmente avec la taille du projet. Les conteneurs de la bibliothèque standard, les itérateurs et la bibliothèque d'algorithmes en sont la preuve.

Si vous pouvez dissocier les algorithmes des structures de données (ou, pour le dire autrement, si vous pouvez dissocier ce que vous faites avec les objets de la manière dont leur état interne est manipulé), vous pouvez réduire le couplage entre vos classes et tirer un meilleur parti du code générique.

Scott Meyers n'est pas le seul auteur à avoir plaidé en faveur de ce principe ; Herb Sutter l'a fait aussi, notamment dans Monolithes non ficelés qui se termine par la ligne directrice :

Dans la mesure du possible, préférez rédiger les fonctions en tant que non-membres non-amis.

Je pense que l'un des meilleurs exemples d'une fonction membre non nécessaire, tiré de cet article, est le suivant std::basic_string::find Il n'y a pas de raison qu'il existe, en réalité, en tant qu'élément de l'économie de marché. std::find offre exactement la même fonctionnalité.

5voto

Dat Chu Points 3810

C'est ce que fait la bibliothèque OpenCV. Elle dispose d'une classe cv::Mat qui présente une matrice 3D (ou des images). Elle dispose ensuite de toutes les autres fonctions de l'espace de noms cv.

La bibliothèque OpenCV est vaste et jouit d'une grande renommée dans son domaine.

4voto

James McNellis Points 193607

L'un des avantages pratiques de l'écriture de fonctions en tant que non membres non amis est qu'elle permet de réduire considérablement le temps nécessaire pour tester et vérifier le code de manière approfondie.

Considérons, par exemple, les fonctions des membres du conteneur de la séquence insert y push_back . Il existe au moins deux approches pour mettre en œuvre push_back :

  1. Il peut simplement appeler insert (son comportement est défini en termes de insert Quoi qu'il en soit)
  2. Il peut faire tout le travail que insert (éventuellement en appelant des fonctions d'aide privées) sans appeler réellement la fonction insert

Il est évident que lors de la mise en œuvre d'un conteneur de séquence, vous voudrez probablement utiliser la première approche. push_back est juste une forme spéciale de insert et (à ma connaissance) il n'est pas possible d'obtenir un quelconque avantage en termes de performances en mettant en œuvre le système push_back d'une autre manière (du moins pas pour les list , deque o vector ).

Cependant, pour tester en profondeur un tel conteneur, il faut tester push_back séparément : puisque push_back est une fonction membre, elle peut modifier tout ou partie de l'état interne du conteneur. Du point de vue des tests, vous devriez (devez ?) supposer que push_back est mis en œuvre selon la deuxième approche parce qu'il est possible qu'il soit mis en œuvre selon la deuxième approche. Il n'y a aucune garantie qu'elle soit mise en œuvre en termes de insert .

Si push_back est implémenté en tant que nonmember nonfriend, il ne peut pas toucher à l'état interne du conteneur ; il doit utiliser la première approche. Lorsque vous écrivez des tests pour lui, vous savez qu'il ne peut pas casser l'état interne du conteneur (en supposant que les fonctions membres du conteneur soient correctement implémentées). Vous pouvez utiliser cette connaissance pour réduire de manière significative le nombre de tests que vous devez écrire pour exercer complètement le code.

2voto

Je le fais aussi souvent, lorsque cela semble logique, et cela ne pose absolument aucun problème de mise à l'échelle. (bien que mon projet actuel ne fasse que 40000 LOC) En fait, je pense que cela rend le code plus évolutif - cela réduit les classes, les dépendances. Cela vous oblige parfois à remanier vos fonctions pour les rendre indépendantes des membres de la classe - et donc souvent à créer une bibliothèque de fonctions d'aide plus générales, que vous pouvez facilement réutiliser ailleurs. J'aimerais également mentionner que l'un des problèmes communs à de nombreux grands projets est le gonflement des classes - et je pense que préférer des fonctions non membres, non amies, est également utile dans ce cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X