Soutenez-nous
Publicité
+ Répondre à la discussion
Page 1 sur 2 12 DernièreDernière
Affichage des résultats 1 à 20 sur 36
  1. #1
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut Conception de classes et pointeurs

    Bonsoir à tous,

    J'ai trouvé un cas de mise en pratique de mes toutes petites connaissances en C++, c'est l'occasion pour moi de m'en servir et surtout de progresser dans la maitrise de ce langage.

    Plusieurs cours ont déjà été décortiqués mais j'ai encore du mal à faire des choix solides entre pointeurs et variables, allocation statique et allocation dynamique.
    Je souhaite surtout faire de l'objet, le plus rapidement possible.

    A première vue, et pour reproduire un comportement qui m'est familier avec des langages de bien plus haut niveau, je serais tenté de faire du "tout pointeur". Des attributs de mes classes aux paramètres qu'acceptent mes méthodes.
    Cependant, si mes attributs (privés) sont des pointeurs et que mes getters/setters acceptent aussi des pointeurs, on va vite se retrouver avec ce que je crois être un gros problème de fiabilité.

    Soit ce code :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    class A {
      private: std::string *_attrib;
     
      public: std::string *getVal(void) const;
      public: void setVal (std::string *val);
    }
     
    std::string *A::getVal (void) const {
       return this->_attrib;
    }
    void A::setVal (std::string *val){
       this->_attrib = val;
    }
     
     
    main (){
       std::string str1 = "test...";
     
       A *obj = new A();
       obj->setVal(&str1);
     
       str1.erase(4, 3);
     
       cout << *obj->getVal() << endl; // Doit afficher "test" au lieu de "test..."
    }
    On parvient à modifier un élément qui est pourtant privé.

    J'hésite vraiment entre ce risque là (avec des liaisons parfois franchement sales) et l'économie de mémoire qui me semble être substantielle en utilisant des pointeurs.

    Par extension, on peut également se demander si en cas de clone de mon objet de type A, la copie de l'attribut doit être prise en charge explicitement où si la duplication est récursive (d'une autre côté, pourquoi cela serait-il le cas vu que le seul attribut est un pointeur?).

    En d'autres termes, j'ai du mal à trouver une règle générale qui me permettrait de dire si le code est propre ou non.

    Merci de m'en dire plus, bon week end

  2. #2
    Membre chevronné
    Homme Profil pro
    F5(){F5}
    Inscrit en
    avril 2008
    Messages
    473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : F5(){F5}
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : avril 2008
    Messages : 473
    Points : 651
    Points
    651

    Par défaut

    hello,

    1: private et public:
    - Dans bisounours, si tu as un attribut private, tu ne devrais avoir aucun accesseur (get/set), ta variable devrait être manipulée qu'à l'intérieur de ta classe.
    - j'évite de parler héritage pour l'instant.
    - interdire l'accès direct au publique et en accorder l'accès via des getters/setters peut se justifier si tu veux faire un traitement lorsqu'on modifie cette variable. (par exemple, compter le nombre de fois qu'on y accède pour des logs)
    =>de manière générale, si tu peux te passer des getters/setters, passe-t-en, parce qu'àprès tu trimballes des dépendances sur tes classes (qui au passage ne t'offre aucune utilité à part stocker)

    2:services ou conteur de données:
    ya deux types de classes: celles qui stockent et celles qui servent.
    ex: classA{public:int a,b,c;}: ca stocke
    ex: classB{public:sayHello(){}}; ca sert (idem ya une valeur ajoutée).
    De manière générale encore, les classes qui stockent, tu duppliques leur valeur car tu t'en tapes.
    Pour les classes à sémantique d'entité, il n'y a pas de sens qu'elles soient duppliquées, (il faudrait que s'il y a deux classes duppliquées, elles aient la même durée de vie et le même comportement). Pour l'instant considère qu'elles sont pas copiables.

    3:pointeur ou mémoire:
    Tant que tu le peux passes-toi des pointeurs sinon faut que gères toi même la mémoire..
    Tu as besoin des pointeurs quand tu as besoin de faire de l'alloc dynamique (ex: un gros tableau, du polymorphisme).
    Sinon sers-toi de référence, ou de copie de données

    concernant les lois,
    pour getter/setter ya la loi de Déméter, le reste je sais pas.

  3. #3
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Bonjour galérien69, merci pour cette réponse.

    Elle soulève de nouvelles interrogations chez moi

    Citation Envoyé par galerien69 Voir le message
    hello,

    1: private et public:
    - Dans bisounours, si tu as un attribut private, tu ne devrais avoir aucun accesseur (get/set), ta variable devrait être manipulée qu'à l'intérieur de ta classe.
    - interdire l'accès direct au publique et en accorder l'accès via des getters/setters peut se justifier si tu veux faire un traitement lorsqu'on modifie cette variable. (par exemple, compter le nombre de fois qu'on y accède pour des logs)
    =>de manière générale, si tu peux te passer des getters/setters, passe-t-en, parce qu'àprès tu trimballes des dépendances sur tes classes (qui au passage ne t'offre aucune utilité à part stocker)
    Voila un principe de l'objet que j'ignorais :s
    Je connais le principe de l'encapsulation qui m'impose private ou du moins protected pour chacun de mes attributs.
    Ceci dit, je fais des vérifications presque à chacun de mes set.

    - j'évite de parler héritage pour l'instant.
    On va éviter de mettre de l'argent là-dedans oui

    2:services ou conteur de données:
    ya deux types de classes: celles qui stockent et celles qui servent.
    ex: classA{public:int a,b,c;}: ca stocke
    ex: classB{public:sayHello(){}}; ca sert (idem ya une valeur ajoutée).
    De manière générale encore, les classes qui stockent, tu duppliques leur valeur car tu t'en tapes.
    Pour les classes à sémantique d'entité, il n'y a pas de sens qu'elles soient duppliquées, (il faudrait que s'il y a deux classes duppliquées, elles aient la même durée de vie et le même comportement). Pour l'instant considère qu'elles sont pas copiables.
    Pour ce passage, la structure et les interfaces de mes classes sont déjà connues, je ne reviendrai pas là-dessus.
    Quand je parlais de copie, il s'agit bien sur des instances de ces classes
    Le but est d'éviter les conflits lors d'accès concurrents qui peuvent survenir en cas d'utilisation presque abusive des pointeurs.

    3:pointeur ou mémoire:
    Tant que tu le peux passes-toi des pointeurs sinon faut que gères toi même la mémoire..
    Tu as besoin des pointeurs quand tu as besoin de faire de l'alloc dynamique (ex: un gros tableau, du polymorphisme).
    Sinon sers-toi de référence, ou de copie de données
    D'accord

    Sur la question particulière des attributs, est-il préférable de les mettre en référence où en copie dure?
    Le problème va être pour le 1er cas une gestion correcte de la concurrence, cf le cas que j'évoque dans mon 1er post.

  4. #4
    Membre chevronné
    Homme Profil pro
    F5(){F5}
    Inscrit en
    avril 2008
    Messages
    473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : F5(){F5}
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : avril 2008
    Messages : 473
    Points : 651
    Points
    651

    Par défaut

    Sur la question particulière des attributs, est-il préférable de les mettre en référence où en copie dure?
    Aucun des deux.

    Le problème va être pour le 1er cas une gestion correcte de la concurrence, cf le cas que j'évoque dans mon 1er post.
    tu n 'évoques pas la concurrence dans ton 1er post.

    Il n'y a pas de règles. Seulement des bonnes pratiques pour les lignes générales. Il y a des cas ou tu auras comme attributs des pointeurs: ex: construction d'un arbre, utilisation de polymorphisme, optimisation mémoire,...
    Il y a des cas ou tu auras des valeurs copiées/copiables.
    type de base (int,string,..),
    Ca dépend ce que tu fais.

    Enfin pour les accès concurrents, c'est assez délicat, je laisse le soin à d'autres.

  5. #5
    Modérateur
    Avatar de koala01
    Profil pro Philippe Dunski
    Inscrit en
    octobre 2004
    Messages
    9 661
    Détails du profil
    Informations personnelles :
    Nom : Philippe Dunski
    Âge : 42

    Informations forums :
    Inscription : octobre 2004
    Messages : 9 661
    Points : 15 582
    Points
    15 582

    Par défaut

    Salut,

    En gros, tu peux avoir deux sortes de classes :

    Les classes qui ont une sémantique de valeur, qui peuvent exister plusieurs fois avec des valeurs identiques (comme une classe point, ou couleur par exemple)

    Ces classes sont, classiquement :
    • constantes (si tu modifie la composante rouge d'une couleur, tu obtiens une autre couleur )
    • copiables (tu peux avoir deux instances présentant les même caractéristiques à deux adresses mémoire différentes)
    • assignables (tu peux décider de repeindre en bleu une pièce qui est en rouge à l'origine )
    • comparable, au minimum du point de vue de l'égalité
    • peu enclines à entrer dans une hiérarchie de classe (à hériter ou à servir de base à un héritage)

    Et il y a les classes qui ont sémantique d'entités, qui s'identifient, d'une manière ou d'une autre, de manière unique et non ambigüe (comme une classe "personne" dont chaque instance représente une personne particulière, ou une classe "compte bancaire" pour la même raison).

    Ces classes sont, classiquement:
    • non copiables (il ne faut pas qu'il existe deux instances d'un même compte bancaire, car, sinon, les retrait effectués au travers d'une instance ne seraient pas répercutés sur l'autre)
    • non assignables
    • modifiables(pour tout ce qui ne sert pas à l'identification unique et non ambigue du moins)
    • non comparables : tu ne peux comparer que certaines de leurs caractéristiques, comme le numéro du compte ou son solde, mais il n'y a aucun sens à comparer deux compte bancaires dans leur globalité
    • globalement adaptées à intervenir dans une relation d'héritage
    Pour ce qui est des accesseurs (getters) et des mutateurs :
    Il existe, comme l'a fait remarquer galerien69, la loi demeter qui dit que si un objet de type A manipule un objet de type B et que l'objet de type B manipule lui-même un objet de type C, tu ne devrais pas avoir à connaitre le type C pour pouvoir travailler avec le type A.

    A priori, tu ne devrais donc fournir un accesseur sur une donnée que si le fait de fournir l'accès à cette donnée fait partie des services que l'on est en droit d'attendre de la part de la classe :

    Par exemple, un compte bancaire a, très certainement, un membre "solde" qui représente le montant disponible sur le compte.

    Comme on s'attend à pouvoir connaitre ce montant, il est "sensé" de fournir un accesseur dessus

    Par contre, tout le monde est bien conscient qu'une voiture est composée, entre autres, d'un réservoir d'essence.

    Cependant, tout ce que l'utilisateur "habituel" d'une voiture doit savoir à son sujet tient dans "l'interface" que la voiture expose de ce réservoire : la trappe de remplissage, la jauge de niveau, les éventuelles indications permettant d'évaluer la distance que tu peux parcourir, etc: ta classe voiture va donc fournir une série de services relatifs à la présence du réservoir, mais il n'y a strictement aucune raison pour qu'elle donne directement l'accès à celui-ci

    En ce qu concerne les mutateurs, maintenant :

    Déjà, si tu n'expose pas un accesseur, il n'y a strictement aucune raison d'exposer un mutateur

    Et, si tu exposes un accesseur, la présence d'un mutateur se justifie très rarement :

    En effet, tu devrais préférer le fait d'exposer des comportement explicites, comme "ajouterAuSolde" ou "effectuerUnPayement", plutôt que d'exposer un mutateur "setSolde".

    La raison en est toute simple : si tu définis le mutateur "setSolde", cela obligera l'utilisateur à écrire un code proche de
    Code :
    1
    2
    3
    4
    5
    6
    Montant solde = compte.getSolde();
    solde+= montantARajouter;
    /* ou 
    solde-= montantDuPayement;
    */
    compte.setSolde(solde);
    alors que cette logique devrait etre de la responsabilité de la classe CompteBancaire.

    D'autre part, le choix du nom d'un comportement ayant pour objectif de modifier un objet permet bien souvent de lever certaines ambiguités :
    Si tu as une classe MachinChose qui se positionne en utilisant la classe point, le fait d'utiliser le comportement move(distanceX, distanceY) voir moveTo(nouvellePosition)permet bien mieux de se rendre compte qu'il s'agit d'un mouvement et qu'il peut ne pas être "immédiat" (car il faut sans doute le temps de parcourir la distance indiquée) que ne permet de le faire un simple "setPosition"

    Pour ce qui est de l'utilisation de pointeurs ou de références : Si tu veux (ou que tu dois) éviter la copie d'un objet lorsque tu le passes comme paramètre à une fonction, transmet le par référence "chaque fois que c'est possible" et par pointeur "lorsque tu n'as vraiment pas le choix".

    Les seuls cas dans lesquels tu devrait recourir aux pointeurs comme arguments d'une fonction sont:
    1. Le besoin de pouvoir indiquer que l'élément n'existe pas, alors qu'il est impossible de fournir une valeur représentant "la non existence" de l'objet
    2. Le besoin de t'interfacer avec du code C, pour récupérer le premier élément d'un tableau
    Dans tous les autres cas, transmets tes arguments:
    • par référence, (éventuellement constante, si l'objet transmis à la fonction ne doit subir aucune modification) s'il ne s'agit pas d'un type primitif (en gros, dés que sizeof(TonType) est plus grand que sizeof(int)
    • par valeur s'il s'agit d'un type primitif (ou que sizeof(TonType est plus petit ou égal à sizeof(int) ) et que la valeur dans la fonction appelante ne doit pas être modifiée
    • par référence (non constante) si la fonction appelée doit modifier la valeur dans la fonction appelante.
    Note cependant que ce dernier cas aura pour résultat de rendre ta fonction "non pure" car elle a un effet de bord, et qu'elle ne pourra donc pas être utilisée dans un cadre multi threadé

    Enfin, pour ce qui est des différentes variables :
    préfères autant que possible l'utilisation de valeur
    Si tu ne peux pas utiliser des valeurs, préfères les référence "chaque fois que faire se peut" et n'utilises les pointeurs que "lorsque tu n'as vraiment pas le choix"

    Les cas dans lesquels tu n'as pas le choix et qu'il faudra utiliser des pointeurs sont:
    • Le fait de fournir une indication du contenant au contenu (ex : un pointeur "parent")
    • Le fait que la variable soit de type polymorphe (aka : soit dans une hiérarchie de classe ) et que
      • soit la variable est créée par une fonction tierce et tu dois en prendre la responsabilité de la durée de vie
      • soit tu n'est pas en mesure (pour des raisons de portées essentiellement) d'initialiser ta variable directement au moment où tu la décalre
    Note à ce sujet que l'on préférera bien souvent, surtout depuis la sortie de la nouvelle norme, avoir recours aux pointeurs intelligents (unique_ptr, shared_ptr ou weak_ptr) car il se manipulent par valeur ou par référence et qu'ils apportent d'énormes avantages en terme de gestion correcte de la durée de vie

    Enfin, pour ce qui est de l'allocation dynamique : Tu ne devrais avoir à écrire un new que dans le cas d'objets polymorphes

    Pour tout ce qui a trait à la gestion de collections d'objets, reportes toi aux classes prévues à cet effet
    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

  6. #6
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Bonjour Galérien et Koala, vous m'avez gâté, merci

    Je vais tenter de faire honneur à ces réponses de bonne qualité!

    Citation Envoyé par koala01 Voir le message
    En gros, tu peux avoir deux sortes de classes :
    A voir les différents traits que tu cites pour chacune, j'ai clairement plus une approche d'entité même si un faible nombre de mes classes peut rentrer dans une logique de valeur.

    Si je comprends bien et pour en être tout a fait certain, dans le cas de classes d'entité je ne dois pas pouvoir faire ça:
    Code :
    1
    2
    3
     
    monEntite a;
    monEntite b = a; // Copie d'instance
    Par extension il me semble donc obligatoire de transmettre ces instances à une fonction par référence sans quoi le changement de scope provoquera une copie pourtant interdite. Oui?

    Cette copie peut-elle être interdite formellement où elle reste une bonne pratique à laquelle il faut s'astreindre?

    A priori, tu ne devrais donc fournir un accesseur sur une donnée que si le fait de fournir l'accès à cette donnée fait partie des services que l'on est en droit d'attendre de la part de la classe
    Certainement, on est tout à fait d'accord là dessus.
    L'encapsulation m'oblige à mettre mes attributs privés mais pas à fournir des accesseurs/mutateurs dessus. C'est selon les besoins et à ma discrétion, suivant les cas évoqués après dans ton message.

    Pour ce qui est de l'utilisation de pointeurs ou de références [...]
    Les seuls cas dans lesquels tu devrait recourir aux pointeurs comme arguments d'une fonction sont:
    1. Le besoin de pouvoir indiquer que l'élément n'existe pas, alors qu'il est impossible de fournir une valeur représentant "la non existence" de l'objet
    En fait c'était surtout ce point qui me servait d'élément central de réflexion.

    Je viens principalement du java et du PHP, où la différence valeur/référence n'est pas du tout la même qu'en C++ et est altéré par le côté haut niveau des deux langages.
    En PHP il est possible de définir certains paramètres à NULL sans avoir à overloader mes méthodes avec plusieurs signatures (c'est obligatoire en Java).
    En C++, utiliser un paramètre de type pointeur me permettrais de le mettre à NULL si il est optionnel en vérifiant scrupuleusement bien sur dans la fonction sa valeur.

    Si je résume : référence = paramètre obligatoire, pointeur = possibilité d’être NULL.

    J'ai bien compris le reste.

    Note cependant que ce dernier cas aura pour résultat de rendre ta fonction "non pure" car elle a un effet de bord, et qu'elle ne pourra donc pas être utilisée dans un cadre multi threadé
    Dans certains de mes cours d'algorithmique, on devait appeler ça des "paramètres de sortie" des fonctions.
    Justement là on aurait des accès concurrent potentiellement sur les mêmes instances.

    Note à ce sujet que l'on préférera bien souvent, surtout depuis la sortie de la nouvelle norme, avoir recours aux pointeurs intelligents (unique_ptr, shared_ptr ou weak_ptr) car il se manipulent par valeur ou par référence et qu'ils apportent d'énormes avantages en terme de gestion correcte de la durée de vie
    Très bien, c'est enregistré merci.

    Pour tout ce qui a trait à la gestion de collections d'objets, reportes toi aux classes prévues à cet effet
    Alors justement, question importante à mes yeux : les collections.

    Sachant qu'elles peuvent contenir des références plutôt que des valeurs, est-il recommandé de déclarer ces références comme constantes?
    Si on a accès à l'instance d'un membre de la collection, on pourrait éditer le contenu de la collection sans y avoir accès directement.
    Quoi que ce n'est pas exactement le contenu de la collection puisque la collection ne contient que des références. Whaaaaa c'est dur de savoir finalement

    Déclarer des collection de références ne servirait selon moi qu'à économiser de la mémoire, je n'ai pas d'autres usages en tête.


    Citation Envoyé par galerien69 Voir le message
    tu n 'évoques pas la concurrence dans ton 1er post.
    En fait je pensais évoquer la concurrence de l'accès sur str1 : il peut être atteint depuis l'intérieur de la classe A mais aussi depuis le main directement puisque c'est là qu'il a été instancié.

    Avec ces explications supplémentaire, ce cas peut être évité en déclarant _attrib en valeur. C'est logique d'un autre côté, étant privé, il ne peut exister que dans l'instance. Sinon il devient publique dès lors qu'on peut le modifier en dehors de l'instance.


    Bonne soirée et merci encore.

  7. #7
    Modérateur
    Avatar de koala01
    Profil pro Philippe Dunski
    Inscrit en
    octobre 2004
    Messages
    9 661
    Détails du profil
    Informations personnelles :
    Nom : Philippe Dunski
    Âge : 42

    Informations forums :
    Inscription : octobre 2004
    Messages : 9 661
    Points : 15 582
    Points
    15 582

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    A voir les différents traits que tu cites pour chacune, j'ai clairement plus une approche d'entité même si un faible nombre de mes classes peut rentrer dans une logique de valeur.
    C'est souvent le cas

    Très souvent, tu auras un petit nombre de classes ayant sémantique de valeur pour un grand nombre de classes ayant sémantique d'entité, et utilisant les classes ayant sémantique de valeur (de manière directe ou indirecte)
    Si je comprends bien et pour en être tout a fait certain, dans le cas de classes d'entité je ne dois pas pouvoir faire ça:
    Code :
    1
    2
    3
     
    monEntite a;
    monEntite b = a; // Copie d'instance
    tout à fait... mais on parle de sémantique de valeur / d'entité (le terme est important)
    Par extension il me semble donc obligatoire de transmettre ces instances à une fonction par référence sans quoi le changement de scope provoquera une copie pourtant interdite. Oui?
    encore exact, voire, sous la forme d'une référence constante si la fonction ne doit pas la modifier

    Note que le gros intérêt supplémentaire à passer une référence est que tu profites alors du polymorphisme, ce qui est particulièrement utile pour les classes ayant sémantique d'entité
    Cette copie peut-elle être interdite formellement où elle reste une bonne pratique à laquelle il faut s'astreindre?
    Il y a moyen de l'interdire formellement, et il est conseillé de le faire.

    Si tu as un compilateur qui supporte cette nouvelle fonctionnalité (norme C++11), tu peux explicitement indiquer qu'une fonction est supprimée sous la forme de
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Entity
    {
        public:
            /* un constructeur "classique" */
            Entity(/* ... */);
            /* on interdit la copie */
            Entity(Entity const &) = delete;
            /* on interdit l'affectation */
            Entity & operator = (Entity const & ) = delete;
            /* et le destructeur est soit public et virtuel, soit protétgé (et pas
             * forcément virtuel (*) )
             */
             virtual ~Entity();
    } ;
    Si je résume : référence = paramètre obligatoire, pointeur = possibilité d’être NULL.
    Excellent résumé
    Dans certains de mes cours d'algorithmique, on devait appeler ça des "paramètres de sortie" des fonctions.
    Justement là on aurait des accès concurrent potentiellement sur les mêmes instances.
    Alors, tu dois soit passer des références constantes (pour éviter le risque de modifications simultanées) ou placer des mutexes

    Alors justement, question importante à mes yeux : les collections.

    Sachant qu'elles peuvent contenir des références plutôt que des valeurs
    Malheureusement, les collections ne peuvent pas contenir de références

    Elles peuvent contenir des objets (qui sont alors copiés) ou des pointeurs (qui ne sont en réalité que de variables numériques non signées qui contiennent l'adresse à laquelle trouver l'objet du type indiqué)

    Pour tout ce qui est de la gestion d'objets dont le type est une classe ayant sémantique d'entité, il faudra "forcément" passer par un pointeur, bien que l'on préférera (encore une fois) passer par un pointeur intelligent si on a la possibilité de travailler en C++11.

    Si tu n'as pas cette possibilité, il est *relativement* facile de se créer ce genre de classe, ou bien, tu peux aussi utiliser boost
    est-il recommandé de déclarer ces références comme constantes?
    De manière générale, tu as toujours intérêt à déclarer ce qui ne doit pas être modifié comme étant constant... Cela t'éviteras bien des soucis

    Pour rappel, il est également possible de déclarer des fonctions membres comme étant constantes. Seules ces fonctions membres peuvent être appelées depuis un objet (ou une référence) constant(e). D'où le conseil que je donne
    Si on a accès à l'instance d'un membre de la collection, on pourrait éditer le contenu de la collection sans y avoir accès directement.
    Quoi que ce n'est pas exactement le contenu de la collection puisque la collection ne contient que des références. Whaaaaa c'est dur de savoir finalement
    Comme je l'ai dit, les collections ne peuvent pas contenir de références

    Mais il faut savoir que les collections de la STL utilisent toutes la notion d'itérateur dans une version constante et dans une version non constante.

    Si tu travailles sur une collection constante, tu ne pourras utiliser que l'itérateur constant.

    Par contre, si tu travailles sur une collection non constante, tu auras le choix entre le fait d'utiliser l'itérateur constant ou non
    Déclarer des collection de références ne servirait selon moi qu'à économiser de la mémoire, je n'ai pas d'autres usages en tête.
    En fait des collections de pointeurs (de préférence intelligents)...

    Et la principale raison pour le faire est de pouvoir profiter du polymorphisme, car on ne peut en profiter que si l'on travaille avec des références ou des pointeurs
    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

  8. #8
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Bonsoir Koala01,

    Ma compréhension progresse mais je fais encore face à quelques difficultés que je détaille ci-après.

    Citation Envoyé par koala01 Voir le message
    C'est souvent le cas
    encore exact, voire, sous la forme d'une référence constante si la fonction ne doit pas la modifier

    Note que le gros intérêt supplémentaire à passer une référence est que tu profites alors du polymorphisme, ce qui est particulièrement utile pour les classes ayant sémantique d'entité
    Donc je vais passer la plupart de mes arguments par référence, cela ne fait plus de doute.

    Par contre dans le cas d'une classe avec des attributs privés de types complexes n'étant pas des référence, comment sa se passe?
    Je suis bien obligé de faire une copie à un moment, dans mon setter par exemple... à moins de convertir ces attributs privés en références mais ça pose le problème initialement évoqué pour moi.

    Idem - et même plus gênant - dans le cas de méthodes renvoyant une valeur de type complexe.
    Je vais logiquement initialiser une variable locale du même type puis la renvoyer.
    Sauf que renvoyer une référence ou un pointeur d'une variable locale ou temporaire ne va pas fonctionner, donc je suis obligé de faire une copie, ce que j’interdis dans ma classe -> Je suis bloqué?

    Il y a moyen de l'interdire formellement, et il est conseillé de le faire.

    Si tu as un compilateur qui supporte cette nouvelle fonctionnalité (norme C++11), tu peux explicitement indiquer qu'une fonction est supprimée sous la forme de
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Entity
    {
        public:
            /* un constructeur "classique" */
            Entity(/* ... */);
            /* on interdit la copie */
            Entity(Entity const &) = delete;
            /* on interdit l'affectation */
            Entity & operator = (Entity const & ) = delete;
            /* et le destructeur est soit public et virtuel, soit protétgé (et pas
             * forcément virtuel (*) )
             */
             virtual ~Entity();
    } ;
    Merci pour ce code.
    Je travaille actuellement sous MSVC 2012 et j'ai une erreur de syntaxe sur le ';' à la fin des deux lignes "Entity(Entity const &) = delete;" et "Entity & operator = (Entity const & ) = delete;" et je ne comprends pas pourquoi.

    Malheureusement, les collections ne peuvent pas contenir de références

    Elles peuvent contenir des objets (qui sont alors copiés) ou des pointeurs (qui ne sont en réalité que de variables numériques non signées qui contiennent l'adresse à laquelle trouver l'objet du type indiqué)

    Pour tout ce qui est de la gestion d'objets dont le type est une classe ayant sémantique d'entité, il faudra "forcément" passer par un pointeur
    Se pose la question du type que je vais surement utiliser le plus souvent : std::string. C'est plutôt un type de valeur j'ai l'impression, non?

    Si tu n'as pas cette possibilité, il est *relativement* facile de se créer ce genre de classe, ou bien, tu peux aussi utiliser boost
    Bien sur, je ne cherche pas encore les coups et j'utilise Boost dès que possible et les conteneurs de la STL sont faciles à utiliser.


    Merci de répondre à ces quelques dernières questions

  9. #9
    Modérateur
    Avatar de koala01
    Profil pro Philippe Dunski
    Inscrit en
    octobre 2004
    Messages
    9 661
    Détails du profil
    Informations personnelles :
    Nom : Philippe Dunski
    Âge : 42

    Informations forums :
    Inscription : octobre 2004
    Messages : 9 661
    Points : 15 582
    Points
    15 582

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    Par contre dans le cas d'une classe avec des attributs privés de types complexes n'étant pas des référence, comment sa se passe?
    Je suis bien obligé de faire une copie à un moment, dans mon setter par exemple... à moins de convertir ces attributs privés en références mais ça pose le problème initialement évoqué pour moi.
    Il faut comprendre qu'une référence n'est jamais qu'un alias d'un objet qui existe "par ailleurs"...

    Il faut donc bien que cet objet existe ... quelque part

    L'emplacement idéal de ce quelque part est, tout simplement, l'accessibilité privée

    De plus, tu ne devrais pas avoir de "setter"...

    le setter brise l'encapsulation en donnant accès à "un détai d'implémentation" de ta classe, et implique que l'utilisateur de ta classe prend une responsabilité qu'il ne devrait pas avoir.

    En effet, si tu travailles à coup de getter / setters, tu te retrouveras à avoir régulièrement un code proche de
    Code :
    1
    2
    3
    Type temp = obj.getXXX();
    /* logique de manipulation de temp */
    obj.setXX(temp);
    avec, comme résultat, le fait que cette "logique de manipulation" se trouvera dupliquée un peu partout.

    Il est donc beaucoup plus sensé de définir les comportements "qui vont bien" dans ta classe, de manière à ce que l'utilisateur n'ait qu'à fournir les indications qui permettent d'arriver à la bonne valeur.

    Par exemple : imaginons que tu aies une classe qui se positionne sur une fenêtre en fonction d'un point deux dimension.
    Tu ne devrais pas avoir un setPosition, mais plutôt un moveTo(newPosition), voir un move(distanceX, distanceY), qui auront l'énorme avantage d'expliquer réellement ce qui se passe, et qui peuvent donner une indication supplémentaire par rapport à setPosition : le fait que ce n'est peut etre pas "instantané".

    De plus, il faut penser qu'il existe une loi appelée "demeter" qui dit que si tu un objet de type A utilise en interne un objet de type B , tu ne devrais pas avoir besoin de connaitre B pour manipuler ton A.

    Par exemple, on se doute bien qu'une voiture dispose d'un réservoir d'essence.

    Mais il n'y aurait aucun sens à donner un accès direct à ta classe réservoir depuis ta classe voiture : A moins d'être mécanicien et de devoir le remplacer, tu n'as aucun intérêt à savoir où il se trouve exactement, et encore moins à y accéder directement.

    Ce qu'il te faut, par contre, c'est une interface, au niveau de voiture, qui te permet d'interagir indirectement sur ton réservoir, comme :
    • La trappe à carburant, pour en rajouter
    • La jauge à essence pour savoir quand en rajouter
    • Eventuellement toute l'électronique qui se basera sur la quantité de carburant dont tu disposes et/ou le débit actuel pour calculer ta consommation ou ton autonomie restante

    Idem - et même plus gênant - dans le cas de méthodes renvoyant une valeur de type complexe.
    Je vais logiquement initialiser une variable locale du même type puis la renvoyer.
    Sauf que renvoyer une référence ou un pointeur d'une variable locale ou temporaire ne va pas fonctionner, donc je suis obligé de faire une copie, ce que j’interdis dans ma classe -> Je suis bloqué?
    Ben... Pourquoi renvoyer une référence sur une copie de ton membre privé pourquoi ne pas renvoyer directement une référence (de préférence constante) sur le membre en question
    Merci pour ce code.
    Je travaille actuellement sous MSVC 2012 et j'ai une erreur de syntaxe sur le ';' à la fin des deux lignes "Entity(Entity const &) = delete;" et "Entity & operator = (Entity const & ) = delete;" et je ne comprends pas pourquoi.
    Oui, j'aurais du le signaler : MSVC, même dans sa version 2012, ne supporte pas encore cette fonctionnalité de la nouvelle norme

    Il y a encore, malheureusement, énormément de fonctionnalités de C++11 qui ne fonctionnent pas avec VC... Une recherche sur internet devrait te dire ce qui est supporté et ce qui ne l'est pas. Peut etre dans la version 2013
    Se pose la question du type que je vais surement utiliser le plus souvent : std::string. C'est plutôt un type de valeur j'ai l'impression, non?
    C'est, effectivement, un type ayant sémantique de valeur, un peu particulière dans le sens où elle n'est pas forcément constante (il y a toutes les fonctions append et autres qui permettent de modifier la valeur de la chaine )

    Mais, s'il est clair que tu l'utiliseras souvent, j'espère pour toi que ce n'est pas celle que tu utiliseras le plus souvent, car elle pose énormément de problème en terme d'efficacité des comparaisons, par exemple, du simple fait qu'elle nécessite de comparer chaque caractère l'un après l'autre

    Ne me fais pas dire ce que je n'ai pas dit hein ? : la classe std :: string est tout ce qu'il y a de plus stable et de plus correcte en terme d'implémentation, simplement, comme toute chaine de caractères, elle subit les limites de ce genre de concept en ce qui concerne la comparaison.

    Si tu dois comparer quelque chose, le plus rapide est de le faire avec les type primitifs entiers
    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

  10. #10
    r0d
    r0d est déconnecté
    Expert Confirmé Sénior

    Profil pro
    Inscrit en
    août 2004
    Messages
    4 005
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : août 2004
    Messages : 4 005
    Points : 4 937
    Points
    4 937

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    Je travaille actuellement sous MSVC 2012 et j'ai une erreur de syntaxe sur le ';' à la fin des deux lignes "Entity(Entity const &) = delete;" et "Entity & operator = (Entity const & ) = delete;" et je ne comprends pas pourquoi.
    Citation Envoyé par koala01 Voir le message
    Oui, j'aurais du le signaler : MSVC, même dans sa version 2012, ne supporte pas encore cette fonctionnalité de la nouvelle norme
    En attendant cette fonctionnalité, tu peux utiliser l'idiome non-copyable

  11. #11
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Bonsoir

    Citation Envoyé par koala01 Voir le message
    L'emplacement idéal de ce quelque part est, tout simplement, l'accessibilité privée

    De plus, tu ne devrais pas avoir de "setter"...

    le setter brise l'encapsulation en donnant accès à "un détai d'implémentation" de ta classe, et implique que l'utilisateur de ta classe prend une responsabilité qu'il ne devrait pas avoir.

    [...]
    Il est donc beaucoup plus sensé de définir les comportements "qui vont bien" dans ta classe, de manière à ce que l'utilisateur n'ait qu'à fournir les indications qui permettent d'arriver à la bonne valeur.
    Oui je comprends bien cette façon de voir et l'exemple du réservoir est très adapté.
    Mais il est dans la voiture depuis le début... On peut aussi mettre toute sorte de choses dans le coffre de la voiture et en enlever.

    J'ai un cas où utiliser un setter me semble plus simple que de proposer un comportement imbriqué dans la classe.
    Je dispose de plusieurs classes me servant à créer des clients pour des services REST. Ces clients sont configurable au moyen d'un objet qui centralise le métier d'accès et d'écriture de la configuration dans divers fichiers.

    Ce que j'ai l'habitude de faire, c'est de créer une instance de ce gestionnaire de configuration, d'y ajouter la collection de fichier necessaires PUIS de créer une instance de mon client et d'utiliser un setter setCFG() sur mes clients pour leur préciser la configuration à utiliser.
    On peut accéder à cette configuration depuis un getter getCFG().

    Donc dans ce cas, l'instance existe avant tout en dehors de l'objet qui n'est pas copiable. Donc un éventuel setter doit se contenter d'enregistrer une référence dans le client HTTP.
    Si il n'y a pas copie, je peux encore modifier depuis l'extérieur la configuration du client HTTP et cela me semble être une possibilité délicate.

    Je ne peux implanter le métier de gestion de la configuration directement dans le client car ce n'est pas le seul usage que je fais avec mon gestionnaire de cette configuration. Utiliser un setter me semble être le plus flexible.


    Ben... Pourquoi renvoyer une référence sur une copie de ton membre privé pourquoi ne pas renvoyer directement une référence (de préférence constante) sur le membre en question
    Non non, la méthode en question ne renvoie pas la valeur d'un attribut mais créé une instance locale à l'image de ce code :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
    TypeNonCopiable &test (void) const {
       TypeNonCopiable obj;
     
       /* Traitement éventuels sur cet objet */
     
       return obj;
    }
    Ici, je ne peux normalement pas renvoyer de référence ou de pointeur vers ma variable obj puisqu'elle cessera d'exister une fois le scope expiré.
    Mais je ne peux pas non plus la copier.
    => Que faire? return ne fait pas de copie?

    Oui, j'aurais du le signaler : MSVC, même dans sa version 2012, ne supporte pas encore cette fonctionnalité de la nouvelle norme

    Il y a encore, malheureusement, énormément de fonctionnalités de C++11 qui ne fonctionnent pas avec VC... Une recherche sur internet devrait te dire ce qui est supporté et ce qui ne l'est pas. Peut etre dans la version 2013
    Ca c'est dommage.
    Je vais étudier tout ca plus en profondeur et voir en quoi consiste le conseil de r0d, merci à lui.

    Ne me fais pas dire ce que je n'ai pas dit hein ? : la classe std :: string est tout ce qu'il y a de plus stable et de plus correcte en terme d'implémentation, simplement, comme toute chaine de caractères, elle subit les limites de ce genre de concept en ce qui concerne la comparaison.
    Merci de ce conseil
    Je tenterai de faire avec!

    Bonne soirée

  12. #12
    Modérateur

    Homme Profil pro Cyrille
    Network programmer
    Inscrit en
    juin 2010
    Messages
    1 907
    Détails du profil
    Informations personnelles :
    Nom : Homme Cyrille
    Âge : 26
    Localisation : France

    Informations professionnelles :
    Activité : Network programmer

    Informations forums :
    Inscription : juin 2010
    Messages : 1 907
    Points : 4 535
    Points
    4 535

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    Non non, la méthode en question ne renvoie pas la valeur d'un attribut mais créé une instance locale à l'image de ce code :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
    TypeNonCopiable &test (void) const {
       TypeNonCopiable obj;
     
       /* Traitement éventuels sur cet objet */
     
       return obj;
    }
    Ici, je ne peux normalement pas renvoyer de référence ou de pointeur vers ma variable obj puisqu'elle cessera d'exister une fois le scope expiré.
    Mais je ne peux pas non plus la copier.
    => Que faire? return ne fait pas de copie?
    Ca montre quand même une chose : le concept n'est pas maitrisé. Vouloir copier un objet non copiable est signe d'une grosse lacune architecturale amha.
    Ou bien obj est un membre mutable et sert de "cache".

    SInon, on l'oublie trop souvent, mais:
    Code :
    1
    2
    3
    4
    5
    6
    bool test (TypeNonCOpiable& out) const {
     
       /* Traitement éventuels sur cet objet */
     
       return true;
    }
    Mais le principe change : il ne s'agit plus de retourner un objet qui représente l'état de ta classe, mais de ta classe qui agit sur un objet sans altérer son propre état.

  13. #13
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Bien sur que je ne maitrise pas ce genre de concept.

    Peut-être que si le haut niveau n'avait pas une philosophie différente, je les aurais intégré avec plus de rapidité.
    Bref, c'est comme ça. On peut se lancer dans ce genre de débat ou tenter d'apporter une réponse.

    Dans le code que je mettais en exemple dans mon précédent post, le but n'était pas foncièrement de vouloir copier un objet non copiable (je pas maso non plus).
    Néanmoins, compte tenu des bonnes pratiques données tout au long de ce post, j'écris une méthode qui me retourne une instance, créée localement, d'une classe d'entité. Comment je fais? Ça me semble bien compromis si on ne peut en retourner ni instance, référence ou pointeur.

    Je n'apprends pas le C++ sans but. J'ai des dizaines de classes écrites en Java ou en PHP où ce fonctionnement est présent. Je ne veux pas me prendre la tête et reprendre les comportements si possible en respectant quelques bonnes pratiques au passage. C'est pourquoi j'adopte des points de vue qui peuvent paraitre incongrus...

  14. #14
    Modérateur

    Homme Profil pro Cyrille
    Network programmer
    Inscrit en
    juin 2010
    Messages
    1 907
    Détails du profil
    Informations personnelles :
    Nom : Homme Cyrille
    Âge : 26
    Localisation : France

    Informations professionnelles :
    Activité : Network programmer

    Informations forums :
    Inscription : juin 2010
    Messages : 1 907
    Points : 4 535
    Points
    4 535

    Par défaut

    Si tu crées en local l'élément que tu dois retourner, alors tu n'as pas le choix, tu le retournes par copie.

    En vérité tu as une autre option, mais je crains que le grand Koala premier ou son acolyte gbdivers me tombent dessus mawashi en avant si je prononce ce mot : pointeur.
    Allez soyons seigneur, vous m'accorderez un smart pointeur ?

    Mais dans ce cas, la responsabilité de libérer la mémoire allouée revient à l'appelant qui récupère le pointeur alloué en interne de la méthode.
    JAVA et PHP sont de très mauvais exemples à ce niveau, les garbage collector te prennent par la main, et JAVA te camouflent toute l'implémentation réelle d'un objet (référence, pointeur, virtual, ...). Il faut savoir, qu'en gros, un objet JAVA est toujours une référence.

    Sinon tu as toujours l'option que j'ai donné plus haut..

    Ou bien dans le cas de certaines factory, les éléments sont static et retournés par référence ou pointeur.

  15. #15
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Citation Envoyé par Bousk Voir le message
    Si tu crées en local l'élément que tu dois retourner, alors tu n'as pas le choix, tu le retournes par copie.
    Donc impossible d'appliquer le design pattern Entité/Stockage dans ces conditions. J'ai vraiment trop de cas où je vais copier des instances de classes d'entité, ce qui est interdit par la déclaration de la classe elle-même.

    En vérité tu as une autre option, mais je crains que le grand Koala premier ou son acolyte gbdivers me tombent dessus mawashi en avant si je prononce ce mot : pointeur.
    Allez soyons seigneur, vous m'accorderez un smart pointeur ?
    Mon compilateur me jette un warning lorsque je retourne un pointeur d'une instance du scope local.
    Pensant que l'instance disparait lors de l'expiration de ce scope je trouve le warning justifié.
    Travaillant sous VC 2012 comme je l'ai dit, je ne sais pas néanmoins si c'est un caprice Microsoft ou la stricte norme C++.

    JAVA et PHP sont de très mauvais exemples à ce niveau, les garbage collector te prennent par la main, et JAVA te camouflent toute l'implémentation réelle d'un objet (référence, pointeur, virtual, ...). Il faut savoir, qu'en gros, un objet JAVA est toujours une référence.
    C'est justement ça la base du problème je crois.
    Le côté positif c'est que ca va me permettre d'assainir un peu le code.

    Enfin, vu qu'on est tombé d'accord avec koala sur le fait de passer des références en priorité, cet aspect là ne me dépaysera pas trop.

    Ou bien dans le cas de certaines factory, les éléments sont static et retournés par référence ou pointeur.
    C'est astucieux oui.
    Dans mon cas je préfère néanmoins autoriser la copie d'objet en théorie non copiables au lieu de charger mes classes en membres statiques.

    C'est un choix, espérons qu'il ne soit pas bloquant pour la suite.

    Merci et bonne nuit.

  16. #16
    Modérateur
    Avatar de koala01
    Profil pro Philippe Dunski
    Inscrit en
    octobre 2004
    Messages
    9 661
    Détails du profil
    Informations personnelles :
    Nom : Philippe Dunski
    Âge : 42

    Informations forums :
    Inscription : octobre 2004
    Messages : 9 661
    Points : 15 582
    Points
    15 582

    Par défaut

    En fait, tu dois te demander "pourquoi un objet est il non copiable"

    Il y a deux raisons à cela :

    La première, c'est que tu veux être sur que ce qui arrivera à un objet particulier lui sera bel et bien appliqué (tu ne peux pas avoir cette certitude si l'objet existe en deux instances distinctes, dont l'une peut subir une modification dont l'autre ne serait pas au courent).

    La deuxième, c'est que tu te trouves dans une logique d'héritage : Si tu connais un client comme étant une personne et que tu fais une copie de "personne", tu vas sans doute perdre toutes les informations qui t'intéressent et qui on trait au fait que c'est aussi un client.

    Non, ce qu'il faut si une donnée est non copiable, c'est effectivement travailler avec des pointeurs (de préférence intelligent), non seulement pour profiter des possibilités de polymorphisme, mais, aussi, parce que cela permet d'être sur que leur durée de vie n'est pas limitée à la portée d'une fonction.

    Mais rien ne t'empêche de renvoyer, autant que possible, ce qui est pointé par le pointeur afin de renvoyer une référence, qui sera beaucoup plus sécurisante à l'emploi qu'un pointeur

    J'ai un cas où utiliser un setter me semble plus simple que de proposer un comportement imbriqué dans la classe.
    Je dispose de plusieurs classes me servant à créer des clients pour des services REST. Ces clients sont configurable au moyen d'un objet qui centralise le métier d'accès et d'écriture de la configuration dans divers fichiers.

    Ce que j'ai l'habitude de faire, c'est de créer une instance de ce gestionnaire de configuration, d'y ajouter la collection de fichier necessaires PUIS de créer une instance de mon client et d'utiliser un setter setCFG() sur mes clients pour leur préciser la configuration à utiliser.
    On peut accéder à cette configuration depuis un getter getCFG().
    En fait, tu devrais créer une factory, qui se charge d'allouer dynamiquement la mémoire pour ton service REST, et qui le renvoie à ton objet qui doit les gérer.

    Mais, au lieu d'invoquer directement la factory, tu devrait passer par l'objet qui gere les services REST pour lui dire "ajoutes un service qui contient telle et telle information".

    Tu "bazardes" toutes les informations brutes nécessaire à la création du service, éventuellement tu crées des fonctions membres qui prennent un identifiant de service et qui appliquent une modification ou l'autre, et c'est ton objet (au travers de la factory) qui s'occupe de gérer le tout.

    Cela te permettra de n'avoir même plus besoin de connaitre le service REST (ou ses classes dérivées) pour pouvoir en créer à demande : tout ce qu'il faut que tu saches, tenant dans les informations qui leur sont nécessaires.

    De la même manière, si ton objet gère un ou plusieurs service REST, tu n'as sans doute pas besoin de renvoyer d'office tout le service, mais ce peut très bien n'être que "certaines parties qui t'intéressent" pour un usage particulier.

    Et, là encore, tu demanderais à ton objet de te fournir telle information ou, s'il gère plusieurs services, telle information concernant le service répondant à tel identifiant.

    L'idée générale de ce que je propose n'est, en définitive, qu'une application stricte de la loi demeter : Si un objet A qui manipule un objet B en interne, tu n'as, a priori, aucun besoin de connaitre B pour pouvoir manipuler A, et tu ne fournis un accès à B que si "quelque chose" qui manipule A a explicitement besoin de connaitre B
    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

  17. #17
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Citation Envoyé par koala01 Voir le message
    En fait, tu dois te demander "pourquoi un objet est il non copiable"
    En fait pas tellement. Je l'ai bien compris et les deux raisons que tu évoques parlent d'elles-même.

    C'est pour ça aussi que tout paramètre hors types primitifs seront références.
    Par contre dans le cas des instances locales à une fonction ça me pose le soucis que j'essaye d'expliquer depuis hier.

    Non, ce qu'il faut si une donnée est non copiable, c'est effectivement travailler avec des pointeurs (de préférence intelligent), non seulement pour profiter des possibilités de polymorphisme, mais, aussi, parce que cela permet d'être sur que leur durée de vie n'est pas limitée à la portée d'une fonction.
    Donc il faudrait que j'écrive ceci (avec des pointeurs simples, je ne connais pas encore les pointeurs intelligents, il faut que je me penche sur la question):
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
    TypeNonCopiable *test (void) const {
       TypeNonCopiable obj;
     
       /* Traitement éventuels sur cet objet */
     
       return &obj;
    }
    Sauf que ça ne fonctionne pas, sous VC 2012 du moins, j'ai un joli warning m'indiquant que je retourne un pointeur d'une variable locale.

    Peut-être y a-t-il une autre façon de l'écrire?

    En fait, tu devrais créer une factory, qui se charge d'allouer dynamiquement la mémoire pour ton service REST, et qui le renvoie à ton objet qui doit les gérer.
    Le but de la classe gérant la configuration n'est pas de gérer les clients REST mais de leur fournir un accès uniforme à la configuration qui les paramètre.

    En terme de code ca donne pour l'instant quelque chose comme ça:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    // Configuration
    ConfigManager cfg_mngr;
    cfg_mngr.addFile ("config.ini", ConfigManager::INI_HIVE);
     
    // Client
    RESTClient client(cfg_mngr);
     
    // Requêtes
    client.get(...);
    etc...
    ConfigManager et RESTClient peuvent correspondre à des entités.
    En l’occurrence je conserve l'accès à l'instance cfg_mngr. Je pourrais supprimer le fichier que j'ai ajouté, avant de créer l'instance du client, avant de faire une requête.
    Si je tente de faire une requête ensuite, il me dira au mieux que ce que je demande n'est pas compatible avec la configuration. Au pire ça plante.

    Un tel système me permet d'avoir une configuration extensive, dont la structure n'est pas connue à l'avance.
    Je peux étendre RESTClient à l'infini et chacune des classes enfant peut avoir besoin de paramètres différents.

    Si je suis ta proposition, il faudrait que j'ai une "factory" pour chacune.
    Tiens, ces factory vont créer une instance de RESTClient, qui n'est pas copiable... cf le cas au début de ce post.

    ConfigManager me permet de configurer toute sorte de choses et vraiment pas que des RESTClient. Il faudrait donc qu'elle contienne des factory pour tout ce qu'elle permet de paramétrer.

    Est-ce cela la bonne solution?

  18. #18
    Modérateur

    Homme Profil pro Cyrille
    Network programmer
    Inscrit en
    juin 2010
    Messages
    1 907
    Détails du profil
    Informations personnelles :
    Nom : Homme Cyrille
    Âge : 26
    Localisation : France

    Informations professionnelles :
    Activité : Network programmer

    Informations forums :
    Inscription : juin 2010
    Messages : 1 907
    Points : 4 535
    Points
    4 535

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    Donc il faudrait que j'écrive ceci (avec des pointeurs simples, je ne connais pas encore les pointeurs intelligents, il faut que je me penche sur la question):
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
    TypeNonCopiable *test (void) const {
       TypeNonCopiable obj;
     
       /* Traitement éventuels sur cet objet */
     
       return &obj;
    }
    Sauf que ça ne fonctionne pas, sous VC 2012 du moins, j'ai un joli warning m'indiquant que je retourne un pointeur d'une variable locale.

    Peut-être y a-t-il une autre façon de l'écrire?
    Non, on parle d'un vrai pointeur avec allocation dynamique.
    Tu alloues en statique, en fin de fonction obj est détruit, heureusement qu'il y a un warning pour te prévenir : tu retournes quelque chose qui n'exite pas.

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
    TypeNonCopiable *test (void) const {
       TypeNonCopiable* pobj;
     
       /* Traitement éventuels sur cet objet */
     
       return pobj;
    }


    Mais encore une fois, le problème selon moi c'est que de toutes façons il n'y a pas vraiment de raison à ce qu'une classe te retourne un nouvel objet de type entité. Ou alors ça s'apelle une factory.

  19. #19
    Membre du Club
    Profil pro
    Étudiant
    Inscrit en
    janvier 2008
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : janvier 2008
    Messages : 252
    Points : 49
    Points
    49

    Par défaut

    Citation Envoyé par Bousk Voir le message
    Non, on parle d'un vrai pointeur avec allocation dynamique.
    D'accord c'est noté merci.

    Tu alloues en statique, en fin de fonction obj est détruit, heureusement qu'il y a un warning pour te prévenir : tu retournes quelque chose qui n'exite pas.
    J'en ai toujours été convaincu. C'est pour cette raison que j'ai bien dit que ce que j'écrivais ne fonctionnait pas sans pour autant savoir comment faire autrement.

    Mais encore une fois, le problème selon moi c'est que de toutes façons il n'y a pas vraiment de raison à ce qu'une classe te retourne un nouvel objet de type entité. Ou alors ça s'apelle une factory.
    Et on ne peut pas avoir de méthodes de classes se comportant comme des factory? C'est contraire à Déméter?

  20. #20
    Modérateur
    Avatar de koala01
    Profil pro Philippe Dunski
    Inscrit en
    octobre 2004
    Messages
    9 661
    Détails du profil
    Informations personnelles :
    Nom : Philippe Dunski
    Âge : 42

    Informations forums :
    Inscription : octobre 2004
    Messages : 9 661
    Points : 15 582
    Points
    15 582

    Par défaut

    Citation Envoyé par fanfouer Voir le message
    Et on ne peut pas avoir de méthodes de classes se comportant comme des factory? C'est contraire à Déméter?
    En fait, à part Demeter, il y a l'acronyme SOLID qui correspond aux cinq piliers de la programmation orientée objets (entre autres repris par les méthodes agiles et autres, d'ailleurs) que sont
    • S mis pour SRP qui signifie Single Responsability Principle (principe de la responsabilité unique)
    • O mis pour OCP qui signifie Open Close Principle (principe "ouvert / fermé)
    • L mis pour LSP qui signifie Liskov Substitution Principle (principe de substitution de Liskov)
    • I mis pour ISP qui signifie Interface Segregation Principle (principe de ségrégation des interfaces)
    • D mis pour DIP qui signifie Dependancies Inversion Principle (principe d'inversion des dépendances)
    En créant une fonction membre qui agit comme une factory, tu peux estimer que tu contrevient au minimum à trois de ces principes:

    Au SRP d'abord car, même si ta fonction ne fait que cela, et respecte donc le SRP au niveau de la fonction (ce qui n'est déjà pas si mal note ), tu donnes une responsabilité supplémentaire au niveau de la classe à laquelle appartient cette fonction.

    En effet, même si la notion de responsabilité au niveau d'une classe est légèrement étendue par rapport à celle que l'on applique à une fonction dans le sens où les classes prennent généralement des responsabilités plus "génériques" (comprend : qui correspondent à plusieurs comportements particuliers), il est particulièrement difficile de trouver une responsabilité qui puisse englober celle de... créer un objet.

    A part une responsabilité de factory, dont c'est la responsabilité propre, tu pourrais envisager de donner la responsabilité de "gérer la durée de vie des objets" à ta classe, car on pourrait, éventuellement, considérer que la création d'un objet fait partie de la gestion de sa durée de vie, mais...

    Le problème avec demeter (et avec ISP) apparait ici dans le sens où une classe qui n'aurait qu'à gérer la durée de vie des objets devrait pouvoir se contenter de connaitre l'interface de la classe de base des objets dont elle gère la durée de vie.

    Or, si tu la rend, aussi, responsable de la création des objets dérivés, elle devra connaitre tous les types dérivés, et tu ne profiteras plus de cette ségrégation des interfaces.

    On peut d'ailleurs aussi estimer que tu ne respecteras pas "correctement" le principe ouvert fermé dans le sens où, chaque fois que tu créeras un nouveau type dérivé, tu devra venir modifier ton code pour le prendre en compte.

    Enfin, bref... Tout cela pour dire que l'idéal est vraiment de faire en sorte de respecter SRP au niveau de tes classes, en veillant à ce que chaque classe ne doive effectivement s'occuper que d'une seule chose, et donc en veillant à ce que, si tu donne une responsabilité de factory à une classe, elle n'ait rien d'autre à faire
    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

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Liens sociaux

Règles de messages

  • Vous ne pouvez pas créer de nouvelles discussions
  • Vous ne pouvez pas envoyer des réponses
  • Vous ne pouvez pas envoyer des pièces jointes
  • Vous ne pouvez pas modifier vos messages
  •