Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Débat : Redéfinition de méthode et principe de substitution de Liskov

Le , par Ekinoks

0PARTAGES

0  0 
Salut !

J'aurais quelque questions sur des problèmes de redéfinition de méthode en C++. Comme je n'ai pas réussi à trouver de solution (il en existe peu être pas), je vous soumet mes questions ^^;

1> Il m'arrive fréquemment de vouloir empêcher la redéfinition d'une méthode pour les objet qui hérite de ma classe. Il me semble qu'en Java il existe le mot clé "final" pour faire ca... Es qu'il existe un équivalant en C++ ?

2> Es qu'il existe un moyen de forcer l'exécution d'une méthode même si celle ci a était redéfinie et ce a partir de la classe mère ?
Exemple :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
struct A {
   virtual void f() {
      cout<<"code A"<<endl;
   }
};

struct B : public A {
   virtual void f() {
      A::f(); // Existe t'il un moyen de faire cette appelle automatiquement sans que la classe fille est a le faire ?
      cout<<"code A"<<endl;
   }
};
3> Existe t'il un moyen de redéfinir des méthodes templates dans une classe fille ?

Merci pour votre aide.

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 03/02/2009 à 22:17
Salut,

1- Si une méthode ne doit pas être redéfinie, il "suffit" de ne pas la déclarer comme virtuelle

2- non, si tu veux qu'une méthode de la classe mère redéfinie dans la classe fille, il faut l'invoquer de manière explicite.

Par contre, il est possible d'utiliser le concept de l'interface non virtuelle pour t'assurer que certaines parties soient effectuées de manière systématique:
Code : Sélectionner tout
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
27
28
29
30
31
32
33
class Base
{
    public:
        void foo()
        {
            before();
            virtualCall();
            after();
        }
    protected:
        virtual void virtualCall()
        {
            /* partie virtuelle */
        }
    private:
        void before()
        {
            /* pré conditions */
        }
        void after()
        {
            /* post conditions */
        }
};
class Derivee : public Base
{
    /*...*/
    protected:
        virtual void virtualCall()
        {
            /* comportement adapté à la classe dérivée */
        }
};
De cette manière, le comportement polymorphe se limite à ce qui est *réellement* à adapter au type dérivé

3- non... une fonction template (ou une méthode de classe template) est adaptée au type utilisé au moment de la compilation

Tu peux donc envisager une spécialisation partielle ou totale de la classe template de manière à adapter le fonctionnement au type réel, mais tu ne peux pas redéfinir cette fonction.

Enfin, tu ne peux pas la redéfinir dans le sens polymorphe du terme, mais tu peux envisager de la redéfinir dans le sens d'un "recouvrement" de la fonction (la version dérivée de la fonction cachant la version de la classe de base).

Cela implique que, si tu travaille sur des objets se faisant passer pour des instance de la classe de base, et que tu invoque la fonction, ce sera la version de la classe de base qui sera effectivement invoquée.
0  0 
Avatar de Ekinoks
Membre averti https://www.developpez.com
Le 03/02/2009 à 23:28
Merci pour ta reponse koala

Citation Envoyé par koala01 Voir le message
1- Si une méthode ne doit pas être redéfinie, il "suffit" de ne pas la déclarer comme virtuelle
Ha oui j'ai oublié de préciser, c'est dans le cas ou la méthode est déjà déclaré virtuelle dans un ancêtre... je cherche donc a arrêter la propagation du "virtual"

Citation Envoyé par koala01 Voir le message
2- non, si tu veux qu'une méthode de la classe mère redéfinie dans la classe fille, il faut l'invoquer de manière explicite.

Par contre, il est possible d'utiliser le concept de l'interface non virtuelle pour t'assurer que certaines parties soient effectuées de manière systématique
Ok, c'est bien cette méthode que j'utilise pour contourner le problème.
Mais elle a quand meme le désavantage de devoir trouver des nom diffirent.
Pour peu que cette astuce soit réaliser plusieurs foi sur un même méthode et on se retrouver avec une liste de nom : "Call", "virtualCall", "virtualCall2", "virtualCall3"...

Mais bon, si c'est la seul solution, on va faire avec :p

Citation Envoyé par koala01 Voir le message
3-
Erf, ok... c'est bien ce que je craignais :'(
0  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 04/02/2009 à 1:44
Citation Envoyé par Ekinoks Voir le message
Merci pour ta reponse koala

Ha oui j'ai oublié de préciser, c'est dans le cas ou la méthode est déjà déclaré virtuelle dans un ancêtre... je cherche donc a arrêter la propagation du "virtual"
Ce que tu peux faire, c'est partir sur une interface non virtuelle, comme indiqué plus haut, et la redéfinition de l'accessibilité de la méthode qui ne doit plus être réimplémentée de manière à la rendre privée

De cette manière, la méthode virtuelle devient inaccessible au départ de "l'ensemble du code" lorsque l'on dispose de la classe dérivée, et n'est accessible que "depuis les méthodes de la classe" (de base ou dérivée) de manière générale.

N'oublie pas que, bien qu'il soit possible de définir un nombre important d'héritage en cascade, il est généralement "cohérent" d'essayer de limiter les niveaux d'héritage à trois ou quatre (il y a eu une discussion sur le sujet il y a quelques mois... une recherche sur le forum devrait te permettre de la retrouver )


Ok, c'est bien cette méthode que j'utilise pour contourner le problème.
Mais elle a quand meme le désavantage de devoir trouver des nom diffirent.
Pour peu que cette astuce soit réaliser plusieurs foi sur un même méthode et on se retrouver avec une liste de nom : "Call", "virtualCall", "virtualCall2", "virtualCall3"...
En toute logique, tu ne devrais pas te retrouver face à la situation d'avoir plus de trois parties "distinctes" dans ta méthode de base:
  • une partie qui concerne la gestion des "pré conditions", une partie qui concerne la gestion des "post conditions" et une partie, potentiellement polymorphe, qui concerne la gestion particulière au type en cours d'utilisation qui se trouve entre les deux premières citées


Si tu envisage une méthode doSomething(), par exemple, tu peux donc "facilement" décider que les différentes parties seront nommée
doSomethingFirst() (ou doSomethingBefore() ou... n'importe quel terme indiquant le fait que cela survient... au début), doSomethingImpl, pour l'implémentation polymorphe de la fonction et doSomethingLast() (ou doSomethingAfter() ou... n'importe quel terme indiquant le fait que cela survient... à la fin)

S'agissant de méthodes qui ne seront invoquées que dans une seule méthode, du moins pour deux des trois méthodes, le fait qu'elles se retrouvent avec des noms "à charnières et à rallonges" ne posera pas trop de problème

N'oublie pas qu'ici, j'ai décidé qu'il y aurait des pré et des post conditions à vérifier, pour donner un exemple complet de ce qui peut se faire, mais que, dans la réalité des faits, il ne sera pas rare de n'avoir que des pré ou que des post conditions, ce qui limitera d'autant le nombre de "sous méthodes" pour lesquelles il faudra trouver un nom

N'oublie pas non plus que, idéalement, le terme utilisé comme nom de fonction devrait représenter ne serait-ce que succintement le but poursuivi (ou le role joué) par cette fonction, raison pour laquelle je te conseille de simplement rajouter "before","impl"(émentation) ou "after" au nom de la fonction
0  0 
Avatar de white_tentacle
Membre émérite https://www.developpez.com
Le 04/02/2009 à 9:22
une partie qui concerne la gestion des "pré conditions", une partie qui concerne la gestion des "post conditions" et une partie, potentiellement polymorphe, qui concerne la gestion particulière au type en cours d'utilisation qui se trouve entre les deux premières citées
Le gros malheur de cette méthode, c'est qu'elle ne permet pas d'étendre les préconditions... (le require_else de eiffel)

Un de mes gros griefs à C++, toujours pas adressé par C++0x.
0  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 04/02/2009 à 9:26
Citation Envoyé par white_tentacle Voir le message
Le gros malheur de cette méthode, c'est qu'elle ne permet pas d'étendre les préconditions... (le require_else de eiffel)

Un de mes gros griefs à C++, toujours pas adressé par C++0x.
En toute logique, si tu as des pré (ou post) conditions valides pour une classe de base, elles doivent rester valides pour les classes dérivées (extension du principe de substitution).

Si tu dois étendre ces conditions pour l'une des classes dérivées, tu reste tout à fait libre de les étendre dans le comportement polymorphe

Je ne vois donc pas vraiment quel grief tu peux faire à ce sujet
0  0 
Avatar de 3DArchi
Rédacteur https://www.developpez.com
Le 04/02/2009 à 9:31
Citation Envoyé par koala01 Voir le message
En toute logique, si tu as des pré (ou post) conditions valides pour une classe de base, elles doivent rester valides pour les classes dérivées (extension du principe de substitution).
Peut être veut-il dire que ce mécanisme de pré/post condition ne peut s'enrichir en descendant : A<-B<-C. A::MaMethode est coupé en A:: PreMaMethode, A:: PostMaMethode et A::MaMethodeImpl qui est virtuelle. Comment faire pour B:: PreMaMethode, B:: PostMaMethode et B::MaMethodeImpl sans passer par des usines à gaz ?
0  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 04/02/2009 à 9:52
J'allais justement éditer mon dernier message pour apporter une nuance dans ce que je dis et "couper l'herbe sous le pied" à cette remarque

En effet, bien que, dans les messages que j'ai écrits précédemment, je laisse à penser que les comportements vérifiant les conditions (pré et post) ne sont pas polymorphes, rien n'empêche non plus de les rendre polymorphes si nous n'arrivons décidément pas à trouver de plus petit comportement commun à définir dans la classe de base

Après tout, tu peux très bien en arriver à quelque chose comme
Code : Sélectionner tout
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
27
28
29
30
31
32
33
34
35
36
37
38
39
class Base
{
    public:
        void doSomething()
        {
            doSomethingBegin();
            doSomethingImpl();
            doSomethingEnd();
        }
    protected:
        virtual void doSomethingBegin()
        {
            /* ne fait rien car il n'y a pas de données à vérifier */
        }
        virtual void doSometingImpl()
        {
            /* ne fait rien car il n'y a pas de données à manipuler*/
        }
        virtual void doSomethingEnd()
        {
            /* ne fait rien car il n'y a pas de données à vérifier */
        }
};
class Derivee : public Base
{
    protected:
        virtual void doSomethingBegin()
        {
            /* vérifie les données */
        }
        virtual void doSometingImpl()
        {
            /* manipule les données*/
        }
        virtual void doSomethingEnd()
        {
            /*  vérifie les données */
        }
};
Simplement, tu veille à ce que les différents comportements puissent rester "atomiques"
[EDIT]il faut juste prendre en compte le fait que les méthodes virtuelles ne devraient pas être implémentées dans une définition de classe... je l'ai fait ainsi par "paresse naturelle"
0  0 
Avatar de white_tentacle
Membre émérite https://www.developpez.com
Le 04/02/2009 à 9:53
En toute logique, si tu as des pré (ou post) conditions valides pour une classe de base, elles doivent rester valides pour les classes dérivées (extension du principe de substitution).
Pas tout à fait. Relis OOSC .

Soit A::f(int i) qui requiert i > 0

Soit B dérive de A. Tu peux avoir B::f(int i) qui requiert seulement i >= 0

C'est parfaitement LSP-compatible. Si E(A) est l'ensemble des entrées valides de A, alors E(B) est un ensemble contenant au moins E(A). Inversement, S(B) est un sous-ensemble de S(A).

Je ne connais pas de moyen de garantir ça en C++ (et il me semble avoir lu de James Kanze que ce n'est pas possible).
0  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 04/02/2009 à 9:56
Citation Envoyé par white_tentacle Voir le message
Pas tout à fait. Relis OOSC .

Soit A::f(int i) qui requiert i > 0

Soit B dérive de A. Tu peux avoir B::f(int i) qui requiert seulement i >= 0

C'est parfaitement LSP-compatible. Si E(A) est l'ensemble des entrées valides de A, alors E(B) est un ensemble contenant au moins E(A). Inversement, S(B) est un sous-ensemble de S(A).

Je ne connais pas de moyen de garantir ça en C++ (et il me semble avoir lu de James Kanze que ce n'est pas possible).
Dans ce cas, je te ramène à mon dernier message, qui a croisé le tien

EDIT: En outre, il pourrait s'avérer intéressant d'envisager d'inverser la relation d'héritage (ce que l'on peut toujours faire) pour que la condition la moins stricte ( i >= 0) soit vérifiée dans la classe la moins dérivée.

Il est en effet faux de croire que l'héritage n'a pour seul objectif de permettre la spécialisation de la classe de base... Il est des cas (rares, mais qui peuvent se présenter quand même) ou l'héritage aura pour objectif une généralisation de quelque chose de plus spécialisé
0  0 
Avatar de 3DArchi
Rédacteur https://www.developpez.com
Le 04/02/2009 à 10:07
Dans le cas i>0 et i>=0, j'aurais aussi tendance à penser que l'héritage est inversé puisque le strict cas est inclus dans inférieur ou égal.
A mon avis, c'est aussi embêtant si tu veux être cumulatif : A:: Pre(i>=0), A<-B:: Pre(i>5), B<-C:: Pre(i>10)...
0  0