IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Member vs Non-member functions


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut Member vs Non-member functions
    Bonjour,

    J'ai récemment lu cet article et j'ai été surpris par la solution conseillée :

    Je comprend l'argument pour mettre les fonctions qui n'ont pas besoin d'accéder aux données privées comme non-membre mais je ne partage pas cet avis dans la plupart des cas.

    J'ai l'impression que les avantages de mettre ces fonctions comme étant membre (hors opérateurs binaires) sont :

    1. Si jamais on doit optimiser cette fonction par la suite et accéder aux membres privés, il n'y a rien à changer ni à rajouter friend
    2. Facilité d'utilisation pour les templates
    3. Uniformité de la syntaxe
    4. Ressemblance aux autres langages
    5. Évite d’encombrer les espaces de noms


    Et les désavantages cités sont :

    1. Moins d'encapsulation
    2. Temps de compilation plus longs


    Maintenant, regardons les contre arguments donnés pour les avantages :

    1) Pas de contre argument donné, car possibilité non citée dans l'article (uniquement le cas où on optimise déjà la fonction est évoqué, pas une optimisation future imprévue). On peut toujours utiliser friend cependant.
    2) Pas de contre arguments, c'est justement un des cas légitimes selon l'article.
    3) Ici l'article dit que la syntaxe ne sera de toute manière pas uniforme : si on a pas accès (en modification) à la classe, on est obligé de rajouter les fonctions comme non-membre (pourquoi ne pas hériter ?)
    4) et 5) sont des avantages négligeables

    Regardons maintenant les contre arguments pour les désavantages :

    1) Et si ces fonctions qu'on implémente comme étant membre n'utilisent que l'interface public de la classe ? Il n'y a donc pas besoin de modifier ces fonctions lors d'un changement interne à la classe...
    2) Pas de contre argument, il est vrai que tout mettre dans la classe empêche de dissocier les headers.

    Ce qui nous laisse avec :

    Pour : Optimisation future sans utilisation de friend, templates, uniformité avec un ajout dans la hiérarchie de classe mais sans rajouter de fonctions virtuelles
    Contre : Temps de compilation

    Je ne suis donc pas hyper convaincu, pourtant l'auteur en connais beaucoup plus que moi sur le C++ et doit avoir de bonnes raisons. Ais-je oublié quelque chose, y a t-il quelque chose qui est beaucoup plus important qu'il n'y parait ?

    De plus, il me semble que la bibliothèque standard propose toutes ses fonctions associées à une classe (les fonctions génériques ne sont pas associées à une classe) comme membre : par exemple, on aurait pu implémenter vector::empty à partir de l'interface publique (vector::size()==0) sans dégrader les performances. Pourtant, empty est membre (bon peut être par uniformité par rapport à list et ainsi permettre l'utilisation de template...). En tout cas, je ne vois pas d'exemple de fonction liée à une classe, non membre, non opérateur binaire. Si vous en avez un, dites-le moi^^

    Merci,
    NoIdea

  2. #2
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,

    En fait, dans bien des cas, la STL n'est vraiment pas l'exemple à suivre pour une raison bien simple : elle a été implémentée avec l'idée -- tout à fait farfelue au vu des possibilités du C++ -- que tout était objet (en gros, et en caricaturant à peine, dans une idée très "javaiste" du développement).

    Le problème, c'est que les mentalités ont énormément évolués depuis lors, mais que, comme la STL fait partie de la norme, et qu'il y a donc énormément de lignes de code qui l'utilisent comme telle, il est maintenant purement et simplement impossible de faire marche arrière sans "tout casser", et donc sans abandonner l'idée d'une compatibilité ascendante accrue.

    Cependant, l'auteur de ce ticket a tout à fait raison : si tu pars d'une feuille blanche, tu as tout intérêt à utiliser autant que faire se peut les fonctions libres. La principale raison est que toute fonction (qu'il s'agisse -- forcément -- des fonctions membre (publiques) ou de fonctions libres) qui fait référence ou qui utilise une classe doit être considérée comme faisant partie de l'interface publique de la classe en question.

    Ainsi, si tu as une fonction membre d'une classe D qui nécessite un paramètre dont le type est une classe C, on peut considérer que cette fonction membre fait réellement partie de l'interface publique de C (en plus de faire partie de l'interface de D, bien évidemment ).

    Une fois que tu as cette notion "d'interface étendue" en tête, tu te rend compte que l'interface publique d'une fonction finit par être très (mais alors, vraiment très) importante, tend à en compliquer singulièrement l'usage, surtout si toutes les fonctionnalités qui font partie de cette interface publique sont effectivement accessibles depuis un seul et même fichier d'en-tête (comme ce serait le cas si toutes ces fonctions étaient définies sous la forme de fonctions membres).

    Et, bien sur, si l'on déclare toutes ces fonctions dans un seul fichier d'en-tête, les temps de compilation vont littéralement exploser : à chaque fois que les gardes anti inclusion n'auront pas supprimé le contenu du fichier d'en-tête, le compilateur devra forcément lire (et manipuler) le contenu du fichier en entier, ce qui est dommage car, ce faisant, il va peut être "perdre son temps" à définir les symboles correspondant à un grand nombre de fonctions dont nous n'avons absolument pas l'usage dans une situation donnée.

    Du coup, l'idée (qui sert d'ailleurs de base à l'ISP) est de partir du point de vue que, si l'on n'a pas une excellente raison de créer une fonction membre, nous avons sans doute intérêt à créer une fonction libre et, tant qu'à faire, à la déclarer dans un fichier d'en-tête séparé. Cette manière de travailler va permettre plusieurs choses :

    D'abord, tes fichiers d'en-tête deviennent beaucoup plus "courts". Cela facilite le travail qui consiste à se créer une "carte mentale" des fonctionnalités auxquelles ce fichier donne accès (il n'y a rien à faire, on lira beaucoup plus facilement un fichier composé d'une petite centaines de lignes qu'un fichier composés de plusieurs milliers de lignes ).

    De plus, cela permettra au compilateur de n'avoir à créer les symboles que des fonctionnalités dont on a réellement besoin : si tu as besoin d'une fonctionnalité, il est possible que tu aies aussi besoin des fonctionnalités "qui vont bien" avec, mais si tu n'en as pas besoin, pourquoi devrait-il perdre son temps à créer les symboles en question .

    Enfin,les gardes anti inclusion multiples et les déclarations anticipées permettront de faire en sorte de limiter très fortement la taille totale des informations à traiter

    Et puis, cerise sur le gâteau, il reste toujours la possibilité de déclarer une fonction libre comme étant amie d'une classe pour éviter d'avoir à exposer des détails qui seraient de nature à briser l'encapsulation.

    Bref, sous réserve de travailler correctement (comprends: d'utiliser autant que possible les déclarations anticipées et / ou l'amitié quand c'est réellement nécessaire), tu as sans doute "tout à gagner" à ne pas avoir une approche strictement basée sur le paradigme orienté objets. C'est d'autant plus vrai que le langage te permet de le faire, dés lors, pourquoi voudrais tu t'en priver
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Salut,

    En fait, dans bien des cas, la STL n'est vraiment pas l'exemple à suivre pour une raison bien simple : elle a été implémentée avec l'idée -- tout à fait farfelue au vu des possibilités du C++ -- que tout était objet (en gros, et en caricaturant à peine, dans une idée très "javaiste" du développement).
    Il est probable que si la STL devait être refaite, il y aurait des changements. Cependant, celle-ci continue de très bien fonctionner et il me semble que tous ces composants s'interfacent bien. As-tu un exemple précis en tête (en relation avec la question des fonctions membres) ?

    Le problème, c'est que les mentalités ont énormément évolués depuis lors, mais que, comme la STL fait partie de la norme, et qu'il y a donc énormément de lignes de code qui l'utilisent comme telle, il est maintenant purement et simplement impossible de faire marche arrière sans "tout casser", et donc sans abandonner l'idée d'une compatibilité ascendante accrue.

    Cependant, l'auteur de ce ticket a tout à fait raison : si tu pars d'une feuille blanche, tu as tout intérêt à utiliser autant que faire se peut les fonctions libres. La principale raison est que toute fonction (qu'il s'agisse -- forcément -- des fonctions membre (publiques) ou de fonctions libres) qui fait référence ou qui utilise une classe doit être considérée comme faisant partie de l'interface publique de la classe en question.
    Oui, mais dans ce cas, l'interface est tellement étendue qu'elle n'a plus de sens. Regarde juste l'interface que ça donne pour les itérateurs...

    Ainsi, si tu as une fonction membre d'une classe D qui nécessite un paramètre dont le type est une classe C, on peut considérer que cette fonction membre fait réellement partie de l'interface publique de C (en plus de faire partie de l'interface de D, bien évidemment ).

    Une fois que tu as cette notion "d'interface étendue" en tête, tu te rend compte que l'interface publique d'une fonction finit par être très (mais alors, vraiment très) importante, tend à en compliquer singulièrement l'usage, surtout si toutes les fonctionnalités qui font partie de cette interface publique sont effectivement accessibles depuis un seul et même fichier d'en-tête (comme ce serait le cas si toutes ces fonctions étaient définies sous la forme de fonctions membres).

    Et, bien sur, si l'on déclare toutes ces fonctions dans un seul fichier d'en-tête, les temps de compilation vont littéralement exploser : à chaque fois que les gardes anti inclusion n'auront pas supprimé le contenu du fichier d'en-tête, le compilateur devra forcément lire (et manipuler) le contenu du fichier en entier, ce qui est dommage car, ce faisant, il va peut être "perdre son temps" à définir les symboles correspondant à un grand nombre de fonctions dont nous n'avons absolument pas l'usage dans une situation donnée.

    Du coup, l'idée (qui sert d'ailleurs de base à l'ISP) est de partir du point de vue que, si l'on n'a pas une excellente raison de créer une fonction membre, nous avons sans doute intérêt à créer une fonction libre et, tant qu'à faire, à la déclarer dans un fichier d'en-tête séparé. Cette manière de travailler va permettre plusieurs choses :

    D'abord, tes fichiers d'en-tête deviennent beaucoup plus "courts". Cela facilite le travail qui consiste à se créer une "carte mentale" des fonctionnalités auxquelles ce fichier donne accès (il n'y a rien à faire, on lira beaucoup plus facilement un fichier composé d'une petite centaines de lignes qu'un fichier composés de plusieurs milliers de lignes ).

    De plus, cela permettra au compilateur de n'avoir à créer les symboles que des fonctionnalités dont on a réellement besoin : si tu as besoin d'une fonctionnalité, il est possible que tu aies aussi besoin des fonctionnalités "qui vont bien" avec, mais si tu n'en as pas besoin, pourquoi devrait-il perdre son temps à créer les symboles en question .

    Enfin,les gardes anti inclusion multiples et les déclarations anticipées permettront de faire en sorte de limiter très fortement la taille totale des informations à traiter

    Et puis, cerise sur le gâteau, il reste toujours la possibilité de déclarer une fonction libre comme étant amie d'une classe pour éviter d'avoir à exposer des détails qui seraient de nature à briser l'encapsulation.

    Bref, sous réserve de travailler correctement (comprends: d'utiliser autant que possible les déclarations anticipées et / ou l'amitié quand c'est réellement nécessaire), tu as sans doute "tout à gagner" à ne pas avoir une approche strictement basée sur le paradigme orienté objets. C'est d'autant plus vrai que le langage te permet de le faire, dés lors, pourquoi voudrais tu t'en priver
    Dans ce cas, pourquoi ne pas juste garder les constructeurs/operateur=/destructor/fonctions virtuelles dans les classes et ne pas mettre toutes les autres fonctions comme externes, quitte à utiliser friend ?

    Remarque : dans l'argumentation que je mène, je ne suis pas du tout en train de dire qu'il faut déclarer toutes les fonctions comme membre, je dis juste que j'ai l'impression que l'argumentation de l'article (et encore plus la tienne) pousse à ne pas mettre certaines fonctions qui pourraient être membre en externe alors que cela ne vaut pas le coup.

  4. #4
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Comme exemple, il y a begin et end, qui "deviennent" std::begin et std::end.
    Il y a aussi toute l'ancienne version de <functional>

  5. #5
    Expert confirmé
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 292
    Par défaut
    Lis le Notes on Programming de Stepanov. Tu verras que si les fonctions size, empty, begin, end, ... sont membres, c'était surtout pour ne pas s'aliéner des Ayatollah de l'OO qui ne pouvaient pas comprendre que libre ou membre (Il y a une part d'interprétation de ma part que l'on reconnaitra à l'emploi de termes qui impliquent des jugements), c'est juste du sucre syntaxique. D'ailleurs le C++11 a corrigé le tir. Et c'est hyper important, c'est grâce à ça (avec le Koenig Namespace Lookup) que les based range for loops marchent. Et non, la STL est tout sauf OO dans l'esprit. Je ne peux que renvoyer aux nombreuses interventions de son auteur qui se défend de tout OOisme.

    Cf aussi le C++17 qui va encore plus loin dans le OSEF si la fonction est membre ou libre pour l'appel -> http://www.open-std.org/JTC1/SC22/WG...2015/n4474.pdf
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  6. #6
    Membre éprouvé
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 766
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 766
    Par défaut
    Citation Envoyé par leternel Voir le message
    Comme exemple, il y a begin et end, qui "deviennent" std::begin et std::end.
    Pour moi, l'avantage est surtout de pouvoir traiter des tableaux, et pas seulement des conteneurs STL.

    Ce qui me gêne, dans cet article de Meyers, c'est la définition qu'il donne du terme "encapsulation".
    Originellement, le concept d'encapsulation a trait au concept des boîtes noires : on n'a pas connaissance des données stockées dans l'objet.

    Meyers ajoute à cette notion celle des dépendances, qui pour moi est un autre problème. Cela perturbe inutilement le lecteur.
    koala01 avec son concept d'interface étendue tombe dans le même travers.

    Le parti de Meyers et des autres lecteurs de ce fil, consistant à promouvoir les fonctions libres, est légitime. C'est vrai qu'il réduit les dépendances, ce qui apporte pas mal d'avantages.
    Mais il y aussi des inconvénients : je trouve la "carte mentale", telle qu'évoquée par koala01, beaucoup plus lisible dans le cas de la POO. Il est beaucoup plus facile de faire une doc sur des classes qu'avec des fonctions libres, car on doit introduite la notion de concepts (les arguments doivent supporter tels types d'opération...), et au final, on ne sait pas trop avec quoi on peut appeler ces fonctions libres.

    Enfin, si on veut un vrai gain à la compilation, il faut effectivement atomiser les fichiers (comme avec le principe SRP), et ça devient parfois n'importe quoi.

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 393
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 393
    Par défaut
    Euh... J'ai du mal à comprendre comment std::begin et std::end peuvent faire autre chose que wrapper (conteneur)::begin et (conteneur)::end. Pour moi, ces fonctions ont justement besoin d'accéder aux membres privés de la classe, qu'elles passent au constructeur de l'itérateur!

    À quel endroit me suis-je trompé?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. warning: control reaches end of non-void function
    Par loisir1976 dans le forum Débuter
    Réponses: 4
    Dernier message: 22/09/2009, 10h54
  2. Réponses: 3
    Dernier message: 29/01/2009, 09h43
  3. Réponses: 2
    Dernier message: 27/01/2008, 21h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo