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

C++ Discussion :

Problème avec le Liskov Substitution Principle


Sujet :

C++

  1. #1
    Membre averti
    Inscrit en
    Mai 2010
    Messages
    30
    Détails du profil
    Informations forums :
    Inscription : Mai 2010
    Messages : 30
    Par défaut Problème avec le Liskov Substitution Principle
    Salut à tous.
    J'ai une classe B qui hérite de A.
    Voici brièvement le code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class A {
       ....
       bool belongsTo(std::vector<A>);
    };
     
    class B :  public A {
    ...
    };
    Now, j'appelle la méthode "belongsTo" avec B en faisant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    B bb;
    vector<B> listInstancesB; 
    //initialisation de la liste
    isbbInListInstancesB = bb.belongsTo(listInstancesB);
    Et comme certains pourront l'imaginer, j'ai une erreur de type reçu par la méthode belongsTo.
    Je sais que je peux juste surcharger cette méthode résoudre le problème mais
    le code est pratiquement le même.
    Ainsi j'aimerais savoir s'il n'y a pas une conception qui pourrait me faire contourner le problème
    en étant fidèle au principe "Don't Repeat Yourself".
    Merci d'avance pour vos réponses.

  2. #2
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Salut,

    Il existe plusieurs solution pour palier à ce problème mais ça dépend un peu de ce que fait ta méthode belongsTo. D'après son en-tête je dirais qu'elle regarde si l'objet courant appartient au vecteur passé en paramètre.

    Si c'est le cas alors tu ne devrais pas en faire une méthode de classe car le corps de cette méthode ne va pas utiliser les membres de A ou B, ou en tout cas de manière indirecte.

    L'idée c'est de surcharger l'opérateur == qui va se charger de faire la comparaison entre deux objets et de laisser l'utilisateur de ta classe chercher par lui même si la classe est bien dans le vecteur. Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    std::vector<A> va;
    // Remplir va...
    A a = /*...*/;
    if(std::find(va.begin(), va.end(), a) == va.end())
    // Not inside
    else
    // Inside
    Tu peux encapsuler le tout dans une méthode template par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    bool belongsTo(const std::vector<T>& vec, const T& value)
    {
      return std::find(vec.begin(), vec.end(), value) == vec.end();
    }
    Tu peux alors utiliser cette méthode avec des vecteurs de A ou de B ou de C...

    Autre chose, fait attention lorsque tu passes tes arguments au méthode (tu le passes par valeur, il est préférable (suivant les cas) de le passer par const ref — voir faq pour + d'info).

    Une dernière chose, si tu appelles souvent ta méthode belongsTo et que ça devient un goulot d'étranglement pour ton application, disons que belongsTo est appelé toute les secondes sur un vecteur d'1 million d'élément, il faudra que tu considères d'autres techniques pour ne pas à avoir à parcourir ton vecteur en entier. Mais bon, ça relève de l'optimisation et ne t'en fait pas trop ça maintenant, c'est juste pour que tu le saches.

  3. #3
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    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 395
    Par défaut
    belongsTo en tant que vecteur de A, ça me paraît suspect. Es-tu sûr que tu ne veux pas un vecteur de pointeurs à la place?
    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.

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

    Informations professionnelles :
    Activité : aucun

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

    Il faut savoir que le type utilisé pour spécialiser intervient dans la détermination du type définitif de celui-ci.

    Ainsi ClasseTemplate<A> est d'un type différent de ClassTemplate<B> et ce, même si B hérite de A.

    Dans ton exemple, vector<B> est donc différent de vector<A>.

    Un autre problème vient du fait que tu sembles avoir oublié que, si A est destiné à servir de classe de base pour B, tes classes A et B ont d'office sémantique d'entité, ce qui les rend, par nature non copiables, et que tu aurais au minimum du rendre ta classe A non copiable (je t'expliquerai plus loin comment faire ).

    Cette caractéristique se répercutant sur l'ensemble des classes qui pourraient hériter de A aurait fatalement rendu B non copiable, et t'aurais assez rapidement permis de te rendre compte que tu faisais une erreur en essayant de remplir un tableau de B car, pour placer un objet dans une collection, il faut qu'il soit copiable.

    Notes au passage que la transmission d'arguments à une fonction se fait, par défaut, par copie et que, si tu veux éviter cette copie (qui risque de prendre énormément de ressources, de temps, en plus de faire que les traitement que tu pourrais appliquer dans ta fonction belongsTo ne se répercutent pas vers tes objets d'origine), tu devrais passer tes collections d'objets par référence, éventuellement constante (si tu ne veux pas que les différents puissent être modifiés dans la fonction appelée).

    Enfin, pour profiter de la substituabilité sur tes objets, il faut qu'ils soient manipulés sous la forme de pointeurs ou sous la forme de référence.

    Mais on ne peut pas créer un tableau de référence!

    Donc, la solution à ton problème consiste à remplir un tableau de pointeurs vers A et à le transmettre à ta fonction belongsTo.

    Evidemment, cela t'obligera à allouer tes objets de manière dynamique (à coup de new), et donc à veiller à en libérer la mémoire quand tu auras fini de travailler avec eux .

    Au final, la solution consiste en plusieurs points:
    1- rendre systématiquement tes classes non copiables (et non affectables).

    Cela se fait facilement en C++11 en déclarant l'opérateur d'affectation et le constructeur de copie comme delete, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class A{
        public:
            A(A const &) = delete;
            A& operator=(A const &) = delete;
            /* ...*/ 
    };
    Si tu ne disposes pas des opportunités offertes par C++11 (ou si ton compilateur ne te permet pas encore de définir les fonctions delete), tu peux utiliser la classe boost::noncopyable sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class A : private boost::noncopyable{
        /*...*/ 
    };
    et, dans le pire des cas, tu peux déclarer sans les définir tes constructeur de copie et opérateur d'affectation dans l'accessibilité privée sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A{
        /* ... */
        private:
            /* ne SURTOUT PAS fournir d'implémentation pour ces deux 
             * fonctions
             */
            A(A const &);
            A& operator=(A const &);
    };
    Ces trois solutions poursuivent le même objectif : obtenir une erreur de compilation si, d'une manière ou d'une autre, tu essayes de copier un objet de type A (ou de n'importe quel type dérivé de A) .
    2- Travailler avec un tableau de pointeurs sur A.

    Comme cela sous entend le plus souvent utiliser l'allocation dynamique de la mémoire, il est préférable d'utiliser des classes RAIIsantes comme std::unique_ptr si tu disposes de C++11.

    Cela te ferait donc utiliser ta classe vector sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    std::vector <std::unique_ptr<A>> tab;
    /* l'insertion de unique_ptr se faisant par exemple sous la forme de */
    tab.emplace_back(std::unique_ptr<A>(new B));
     
    /* l'accès au pointeur pouvant se faire sous la forme de
    tab[i].get()->someInterestingBehaviour();
    Si tu ne disposes pas de C++11, jettes un oeil du coté des smart_pointers de boost, ils sont d'une utilisation identique

    En dernier recours, tu pourras toujours utiliser des pointeurs nus, mais il devient assez difficile d'assurer un comportement résistant aux exceptions

    3- transmet, systématiquement, tes std::vector (et autres collections) sous la forme de référence à tes fonctions

    Si ta fonction appelée ne doit pas modifier la collection en question, transmet la par référence constante.

    4- Penses que, en C++, les fonctions ne sont pas virtuelles par défaut. Si tu veux qu'une fonction (le destructeur par exemple) puisse adapter son comportement en fonction du type réel de l'objet à partir duquel elle est appelée, tu dois la déclarer comme virtuelle

    Au final, ta classe A ressemblera donc à quelque chose comme (c++11 inside)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class A {
       public:
           A(A const &)  = delete;
           A & operator=(A const & ) = delete;
           virtual ~A();
       ....
       bool belongsTo(std::vector<std::unique_ptr<A>> /* const */ &);
    };
    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

  5. #5
    Membre averti
    Inscrit en
    Mai 2010
    Messages
    30
    Détails du profil
    Informations forums :
    Inscription : Mai 2010
    Messages : 30
    Par défaut
    Merci à vous pour vos réponses très bien illustrées e veillez m'excuser pour ma réponse tardive.
    Trademark, ta solution me convient parfaitement. En fait j'utilise la fonction find dans l'implémentation de belongsTo et de plus je n'utilise pas les membres de A et B dans cette méthode donc l'externalisation de cette méthode me semble un bon choix. Concernant le passage par référence de vector<A>, j'y avais pensé met le compilateur me signale une erreur au niveau de cet bout de code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A::belongsTo(vector<A> listA) {
       vector<A>::iterator result = find(listA.begin(), listA.end(), *this);
       return result != listA.end();
    }
    La ligne en rouge est l'endroit où le compilateur me signale une erreur. Après avoir bien lu "les reproches du compilateur", j'ai mieux compris les propos de koala01 concernant le caractère non copiable des objets. C'est un concept très important que j'ignorais jusqu' à présent!
    Je crois que je vais utiliser un vector de pointeur de B, car cela accélèrera la ré-allocation interne du vector.
    Merci à tous encore pour tous ces "insights", je viens d'apprendre énormément tant en conception OO qu'en subtilités du langage C++.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Problème avec la substitution de commandes
    Par essomba84 dans le forum Shell et commandes GNU
    Réponses: 4
    Dernier message: 31/10/2011, 18h53
  2. VC++ Direct3D8, problème avec LPD3DXFONT et LPD3DTEXTURE8
    Par Magus (Dave) dans le forum DirectX
    Réponses: 3
    Dernier message: 03/08/2002, 11h10
  3. Problème avec le type 'Corba::Any_out'
    Par Steven dans le forum CORBA
    Réponses: 2
    Dernier message: 14/07/2002, 18h48
  4. Problème avec la mémoire virtuelle
    Par Anonymous dans le forum CORBA
    Réponses: 13
    Dernier message: 16/04/2002, 16h10

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