|
|
|
|
||||||
![]() |
|
|
LinkBack | Outils de la discussion |
|
|
#1 |
|
Messages: n/a
Hébergeur: |
Bonjour,
Si on a les trois classes suivantes : class A { public: virtual A & operator=(A const & rvalue) ; /* ... */ } ; class B0 : public A { public: virtual A & operator=(B0 const & rvalue) ; /* ... */ } ; class B1 : public A { public: virtual A & operator=(B1 const & rvalue) ; /* ... */ } ; Dans l'usage suivant : B0 b0 ; B1 b1 ; A & b0_a = static_cast<A &>(b0) ; b1 = b0_a ; Dans la pratique, comment devrais-je gérer une telle affectation ? Lever une exception car c'est un non sens ? Et surtout, comment je le détecte dans l'affectation ? La solution la plus directe que je vois dans le cas où on gère est un dynamic_cast dans B0: perator=(A const&). J'avoue que je serais partisan de rendre l'opération silencieuse, mais je risque de me surprendre, en perdant des information lors d'affectations. Que faites-vous pratiquement dans ce cas ? Merci ! -- Mickaël Wolff aka Lupus Michaelis http://lupusmic.org |
|
|
|
#2 |
|
Messages: n/a
Hébergeur: |
Mickaël Wolff wrote on 30/04/2008 02:59:
> > class A { > public: > virtual A & operator=(A const & rvalue) ; > } ; > > class B0 : public A { > public: > virtual A & operator=(B0 const & rvalue) ; > } ; > > class B1 : public A { > public: > virtual A & operator=(B1 const & rvalue) ; > } ; > > B0 b0 ; > B1 b1 ; > A & b0_a = static_cast<A &>(b0) ; > b1 = b0_a ; > > Dans la pratique, comment devrais-je gérer une telle affectation ? elle set impossible comme telle. B1 définit un operateur = avec B1& en paramètre, pas un A& > Lever une exception car c'est un non sens ? Et surtout, comment je le > détecte dans l'affectation ? si l'affectation est un non-sens, les operéateurs d'affectation devraient être privés pour éviter ces non-sens. > La solution la plus directe que je vois dans le cas où on gère > est un dynamic_cast dans B0: perator=(A const &).si en effet initialiser un B1 depuis un A a un sens (affectation des attributs de base et déduction et/ou choix par défaut pour les membres propres à B1) cet opérateur sur B0 et/ou B1 est requis. > J'avoue que je serais partisan de rendre l'opération silencieuse, > mais je risque de me surprendre, en perdant des information lors > d'affectations. si la perte signifie que l'instance sera caduque, interdissez les affectations; si les classes sont transposables (A = coordonnées, B0 = cartesiennes, B1 = polaires) offrez ces opérateurs et pourquoi pas un B0: perator= (B1 const&) (et vice-versa).Sylvain. |
|
|
|
#3 |
|
Messages: n/a
Hébergeur: |
On Apr 30, 2:59 am, Mickaël Wolff <mickael.wo...@laposte.net> wrote:
> Si on a les trois classes suivantes : > class A > { > public: > virtual A & operator=(A const & rvalue) ; > /* ... */ > } ; > class B0 : public A > { > public: > virtual A & operator=(B0 const & rvalue) ; > /* ... */ > } ; > class B1 : public A > { > public: > virtual A & operator=(B1 const & rvalue) ; > /* ... */ > } ; > Dans l'usage suivant : > B0 b0 ; > B1 b1 ; > A & b0_a = static_cast<A &>(b0) ; > b1 = b0_a ; > Dans la pratique, comment devrais-je gérer une telle > affectation ? Dans la pratique, tu dois te démander si ça a un sens. Au moins dans les applications que je fais, le polymorphisme s'applique surtout à des objets ayant une identité, et donc, qui ne supporte ni l'affectation ni la copie. Sinon, il faut que tu définisses ce que ça signifie dans des cas comme ceci. Considère, par exemple, ton code. D'abord, virtuel ou non, tes opérateurs d'affectation dans les classes dérivées ne supplantent pas la fonction virtuelle dans la classe de base, parce qu'ils n'ont pas les mêmes paramètres. Pour qu'il y a supplantage, il faut définir un opérateur avec la même signature dans les classes dérivées, c-à-d : class B0 : public A { { public: virtual B0& operator=( A const& other ) ; // ... } ; (Que l'opérateur renvoie un B0& ou un A&, en revanche, n'a pas d'importance, puisque le C++ supporte les types de retour co-variants.) Du coup, ton code ne passe pas le compilateur, parce que l'opérateur d'affectation de b1 exige bien un B1 (l'opérateur d'affectation dans B1 cache bien celui dans A), et tu lui passe un A. (Pour les paramètres, il n'y a que le type statique qui compte.) > Lever une exception car c'est un non sens ? Est-ce un non sens, en voilà la question ? Dans les rares cas où l'affectation d'un type polymorphique aient du sens, ce n'est pas forcement un non sens. Encore, tout dépend. Je vois tout de suite trois possibilités : 1. On n'accepte l'affectation qu'entre des types dynamiques identiques. Mais dans ce cas-là, je me démande si on veut réelement l'affectation (ou la polymorphisme) ; pour certaines opérations (l'affectation), on n'est pas du tout polymorphique, et pour d'autres si. C'est en tout cas une violation flagrante du LSP. S'il le faudrait, j'interdirais l'affectation au niveau de la classe de base, et ne l'implémenterait qu'au niveau des classes dérivées. Si le client veut affecter, il faut qu'il sache avoir des instances de la même dérivée, et qu'il utilise leur interface. (Par exemple, qu'il ait fait un dynamic_cast avant, pour traiter un cas spécial.) Dans la pratique, ce cas arrive facilement quand la classe de base représente une qualité plutôt indépendante du rôle de la classe, quelque chose comme PersistentObject. Dans ce cas-là, en revanche, on ne traite avec la classe de base que dans les fonctions qui gèrent cette qualité, ici par exemple, les fonctions d'entrée/sortie. Et l'affectation se fera bien toujours avec des classes dérivées. 2. Les classes dérivées représentent une famille, avec un état abstrait commun. (Je ne peux malheureusement pas penser à de bons exemples ; je crois que le cas ne s'est jamais présenté dans mes applications.) Dans ce cas-là, il s'agit d'écrire un opérateur d'affectation qui extrait l'état commun, quelque soit le type à droit. Dans le cas le plus général, il faudrait le « double dispatch », que la fonction réelement appelée dépend à la fois du type de l'objet à gauche, et de celui à droit. Dans ce cas-ci, je ferais plutôt une fonction virtualle assign() dans la classe de base, avec des surcharges pour toutes les classes dérivées (implémentation classique du double dispatch), ou qui cherche la fonction à appeler dans un map (autre implémentation du double dispatch) ; l'opérateur d'affectation se contentera d'appeler cette fonction. Ou les opérateurs d'affectation, plutôt ; il en faudrait au moins deux dans chaque classe dérivée -- un de copie, pour empêcher que le compilateur en génère le sien, et un qui prend une référence à la classe de base, pour pouvoir affecter les d'autres types dérivés. 3. Enfin, on a réelement à faire avec une valeur polymorphique, dont le type dynamique fasse partie de la valeur, et doit être changé lors de l'affectation. Dans ce cas-ci, il faut l'idiome du lettre/enveloppe. C-à-d que le type de base contient un pointeur à l'objet réelement polymorphique, et y renvoie tous les opérations. Et que tous les objets déclarés soit du type de base, et que l'affectation fasse un clone de l'objet contenu par l'objet à droit. Ça marche pas mal, mais c'est un peu lourd, avec un clonage à chaque affectation ou copie. (Dans le cas où les objets sont immutable, en dehors de l'affectation, on peut éviter le clonage en se servant d'un glaneur de cellules, ou éventuellement d'un comptage de références. Ce qui le rend réelement utilisable, dans beaucoup de cas.) > Et surtout, comment je le détecte dans l'affectation ? La détection, c'est le problème la plus simple : if ( typeid( *this ) != typeid( other ) ) ... Savoir ce qu'il faut faire (c-à-d la conception) qui est plus difficile. > La solution la plus directe que je vois dans le cas où on gère > est un dynamic_cast dans B0: perator=(A const &). J'avoue que> je serais partisan de rendre l'opération silencieuse, mais je > risque de me surprendre, en perdant des information lors > d'affectations. > Que faites-vous pratiquement dans ce cas ? Je les évite, autant que possible. Dans la pratique, je trouve qu'ils apparaissent assez rarement, et quand ils apparaissent, ou bien, je me trouve dans le cas 1, ci-dessus, mais celui qui veut affecter sait toujours très bien le type (suite à un dynamic_cast au retour de la lecture, par exemple), et le problème ne se pose pas réelement, ou je me trouve dans le cas 3, mais avec des objets immutable, et je m'en tire en simplement affectant un pointeur (intelligent, genre boost::shared_ptr, si je n'ai pas de glaneur de cellules). -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#4 |
|
Messages: n/a
Hébergeur: |
Sylvain SF a écrit :
> elle set impossible comme telle. > B1 définit un operateur = avec B1& en paramètre, pas un A& En fait si. En fait, B0: perator=(B0 const &) empêche la créationd'un operator= automatique. Un effet de bord est donc l'appel de A: perator=(A const &) à l'affectation d'un A. C'est du moins lecomportement que j'observe avec gcc 4.3.0, et il ne me parait pas aberrant. >> Lever une exception car c'est un non sens ? Et surtout, comment je le >> détecte dans l'affectation ? > > si l'affectation est un non-sens, les operéateurs d'affectation > devraient être privés pour éviter ces non-sens. Effectivement, c'est une solution à envisager. > si la perte signifie que l'instance sera caduque, interdissez les > affectations; si les classes sont transposables (A = coordonnées, > B0 = cartesiennes, B1 = polaires) offrez ces opérateurs et pourquoi > pas un B0: perator= (B1 const&) (et vice-versa).Merci pour ta réponse. Je répondrai à James un peu plus tard, car sa réponse va me demander un peu beaucoup de réflexions et d'essais avant de pouvoir apporter une réponse pertinente. -- Mickaël Wolff aka Lupus Michaelis http://lupusmic.org |
|
|
|
#5 |
|
Messages: n/a
Hébergeur: |
On Apr 30, 2:14 pm, Mickaël Wolff <mickael.wo...@laposte.net> wrote:
> Sylvain SF a écrit : > > elle set impossible comme telle. B1 définit un operateur = > > avec B1& en paramètre, pas un A& > En fait si. En fait, B0: perator=(B0 const &) empêche la> création d'un operator= automatique. Un effet de bord est donc > l'appel de A: perator=(A const &) à l'affectation d'un A.> C'est du moins le comportement que j'observe avec gcc 4.3.0, > et il ne me parait pas aberrant. En fait, non. Tu ne trouves l'operator=( A const& ) que parce que tu affectes à travers un A. Rappelons comment se passe les choses : -- Le compilateur recherche le nom (ici, operator=). Dans le cas d'un opérateur, cette recherche est un peu spéciale, parce qu'il considère à la fois des fonctions libres et des fonctions membre de la classe. (Il fait deux recherches plus ou moins indépendantes.) Mais dans le cas de l'opérateur d'affectation, ça ne change rien, parce qu'on n'a pas le droit d'en fournir un qui n'est pas membre. La recherche du nom se fait selon les types statiques (obligatoirement, puisque c'est tout ce que connaît le compilateur, et s'arrête là où le nom est trouvé pour la première fois. C-à-d que si on recherche un operator= avec à gauche de l'affectation un B0, le compilateur va trouver B0: perator=. Toujours, parce que cette fonction ne peutpas ne pas exister. Et il s'arrête là ; il ne regarde pas dans les classes de base. -- Ensuite, il y a la résolution des surcharges, mais seulement parmi les fonctions que le compilateur a trouvé avant. Donc, dans le cas où l'expression à gauche de l'affectation a le type B0, que parmi les operator= de B0. Si tu essaies d'affecter un A (ou une expression dont le type statique est A, indépendamment du type dynamique de l'objet), et que B0 n'a pas elle-même un operator= qui prend un A, le compilateur doit te rejeter. -- Enfin, seulement une fois la résolution du surcharge finie, le compilateur considère si la fonction est virtuelle ou non. Donc, avec les classes que tu avais définies : B0* pba = new B0 ; B0* pbb = new B0 ; A* paa = pb0a ; A* pab = pb0b ; *paa = *pab ; // légal, appelle A: perator=(A const&)*paa = *pbb ; // légal, appelle A: perator=(A const&)*pba = *pab ; // illégal *pba = *pbb ; // légal, appelle B0: perator=(B0 const&)Si, dans A, l'operator=(A const&) est virtuel, *et* il est supplanté dans B0 (avec un operator=(A const&)), alors, les deux premiers appels seront virtuels, et appelleront B0: perator=( A const& ). Mais il faut bien les deuxconditions : et que la fonction en A soit virtuelle, et qu'il y a un supplantage avec exactement les mêmes paramètres (et la même const-ité) dans B0. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#6 |
|
Messages: n/a
Hébergeur: |
James Kanze a écrit :
> Dans la pratique, tu dois te démander si ça a un sens. Au moins > dans les applications que je fais, le polymorphisme s'applique > surtout à des objets ayant une identité, et donc, qui ne > supporte ni l'affectation ni la copie. Qu'est-ce que tu appelles une identité ? > class B0 : public A { > { > public: > virtual B0& operator=( A const& other ) ; > // ... > } ; Oui, en fait j'ai mal posé mes exemples. Finalement, dans le cas où j'avais besoin d'affectations et de comparaisons virtuelles pour me libérer de la nature des objets. > (Que l'opérateur renvoie un B0& ou un A&, en revanche, n'a pas > d'importance, puisque le C++ supporte les types de retour > co-variants.) Ça j'avais compris ![]() > 1. On n'accepte l'affectation qu'entre des types dynamiques > identiques. Mais dans ce cas-là, je me démande si on veut > réelement l'affectation (ou la polymorphisme) ; pour > certaines opérations (l'affectation), on n'est pas du tout > polymorphique, et pour d'autres si. C'est en tout cas une > violation flagrante du LSP. LSP ~= interchangeabilité ? > S'il le faudrait, j'interdirais l'affectation au niveau de > la classe de base, et ne l'implémenterait qu'au niveau des > classes dérivées. Si le client veut affecter, il faut qu'il > sache avoir des instances de la même dérivée, et qu'il > utilise leur interface. (Par exemple, qu'il ait fait un > dynamic_cast avant, pour traiter un cas spécial.) Ok. > Dans la pratique, ce cas arrive facilement quand la classe > de base représente une qualité plutôt indépendante du rôle > de la classe, quelque chose comme PersistentObject. Dans ce > cas-là, en revanche, on ne traite avec la classe de base que > dans les fonctions qui gèrent cette qualité, ici par > exemple, les fonctions d'entrée/sortie. Et l'affectation se > fera bien toujours avec des classes dérivées. C'est certainement ce qui m'arrive. Pour être concret, j'ai une classe abstraite message. Cette classe fournit des services de base (canal d'adressage du message), et enveloppe les données transmises. J'ai essayé de penser à un système générique pour pouvoir les envoyer de la vue au contrôleur et du modèle à la vue. > 3. Enfin, on a réelement à faire avec une valeur polymorphique, > dont le type dynamique fasse partie de la valeur, et doit > être changé lors de l'affectation. Dans ce cas-ci, il faut > l'idiome du lettre/enveloppe. C'est marrant que tu parles d'enveloppe alors que j'envoie des messages. > La détection, c'est le problème la plus simple : > > if ( typeid( *this ) != typeid( other ) ) ... Certes. > Je les évite, autant que possible. Dans la pratique, je trouve > qu'ils apparaissent assez rarement, et quand ils apparaissent, > ou bien, je me trouve dans le cas 1, ci-dessus, mais celui qui > veut affecter sait toujours très bien le type (suite à un > dynamic_cast au retour de la lecture, par exemple), et le > problème ne se pose pas réelement, ou je me trouve dans le cas > 3, mais avec des objets immutable, et je m'en tire en simplement > affectant un pointeur (intelligent, genre boost::shared_ptr, si > je n'ai pas de glaneur de cellules). Merci pour toutes ces infos, ça m'a bien aidé à avancer. Même si je suis de plus en plus convaincu d'avoir écrit une n-ième usine à gaz. :-/ Pas grave, je finirais par appliquer KISS ! Un jour. -- Mickaël Wolff aka Lupus Michaelis http://lupusmic.org |
|
|
|
#7 |
|
Messages: n/a
Hébergeur: |
On May 7, 2:02 am, Mickaël Wolff <mickael.wo...@laposte.net> wrote:
> James Kanze a écrit : > > Dans la pratique, tu dois te démander si ça a un sens. Au moins > > dans les applications que je fais, le polymorphisme s'applique > > surtout à des objets ayant une identité, et donc, qui ne > > supporte ni l'affectation ni la copie. > Qu'est-ce que tu appelles une identité ? C'est qu'une opération sur un objet n'en vaut pas la même opération sur un autre, même si la « valeur » est la même. L'exemple typique (mais bien simplifié), c'est un compte en banque, et une valeur qu'on y vire ou prélève. Le compte a une identité ; faire la virement sur une copie de l'objet, ou un autre objet qui a par hazard la même valeur ne va pas. La valeur qu'on y vire ou prélève, en revanche, n'a pas d'identité : 100 Euros, c'est 100 Euros, que ce soit une copie ou non. (Dans la pratique, il arrive qu'on fasse les copies des objets ayant une identité, dans la gestion des transactions, par exemple. Mais chaque opération s'effectue quand même sur une instance précise de l'objet. On ne copie ni n'affecte pas librement, mais seulement dans une contexte bien définie -- où il serait bien acceptable, voire préférable, de faire l'affectation ou la copie au moyen d'une fonction explicite, plutôt que de se servir de l'opérateur.) > > class B0 : public A { > > { > > public: > > virtual B0& operator=( A const& other ) ; > > // ... > > } ; > Oui, en fait j'ai mal posé mes exemples. Finalement, dans le > cas où j'avais besoin d'affectations et de comparaisons > virtuelles pour me libérer de la nature des objets. Les comparaisons ne présentent pas forcément tous les problèmes des affectations. Si on suppose que le type dynamique fasse partie de la « valeur », == renvoie faux dès que les types sont différents (ce qui peut se faire simplement avec typeid). Tandis que l'affectation n'est tout bonnement impossible entre des types (dynamiques) différents. > > (Que l'opérateur renvoie un B0& ou un A&, en revanche, n'a pas > > d'importance, puisque le C++ supporte les types de retour > > co-variants.) > Ça j'avais compris ![]() > > 1. On n'accepte l'affectation qu'entre des types dynamiques > > identiques. Mais dans ce cas-là, je me démande si on veut > > réelement l'affectation (ou la polymorphisme) ; pour > > certaines opérations (l'affectation), on n'est pas du tout > > polymorphique, et pour d'autres si. C'est en tout cas une > > violation flagrante du LSP. > LSP ~= interchangeabilité ? Liskov Substitution Principle. En fait, oui, c'est un peu l'interchangeabilité -- on doit pouvoir utiliser un Derived partout ou un Base est démandé. En fait, c'est lié à la notion du contrat : la base définit un contrat, et tout dérivée doit le respecter. Et la substitutabilité ne vaut, évidemment, que dans le cas où le client respecte sa partie du contrat. (On peut, par exemple, imaginer le cas où le code client ne marche qu'à cause des aléas de l'implémentation de la base, et non à cause de quelque chose de garantie par la base.) En gros, la dérivée ne doit pas imposer des préconditions plus contraignantes, et doit garantir des postconditions au moins aussi fortes que la base. Or, dans ce que tu proposes, l'affectation d'une dérivée a la précondition que ce qu'on affecte ait le type dérivée, et non simplement base. Mais comme d'habitude, ce n'est pas toujours aussi simple. Le contrat peut être que l'affectation n'est admise que si certaines conditions sont rempli. (Dans la bibliothèque standard, par exemple, le contrat d'affectation d'un itérateur exige que l'itérateur à droit de l'affectation n'ait pas une valeur singulaire, c-à-d qu'il ne soit pas construit par le constructeur par défaut.) Mais personnellement, je n'aime pas trop que l'opérateur d'affectation ait des préconditions ; dans de tels cas, je préfère de loin l'utilisation d'une fonction nommée. > > S'il le faudrait, j'interdirais l'affectation au niveau de > > la classe de base, et ne l'implémenterait qu'au niveau des > > classes dérivées. Si le client veut affecter, il faut qu'il > > sache avoir des instances de la même dérivée, et qu'il > > utilise leur interface. (Par exemple, qu'il ait fait un > > dynamic_cast avant, pour traiter un cas spécial.) > Ok. > > Dans la pratique, ce cas arrive facilement quand la classe > > de base représente une qualité plutôt indépendante du rôle > > de la classe, quelque chose comme PersistentObject. Dans ce > > cas-là, en revanche, on ne traite avec la classe de base que > > dans les fonctions qui gèrent cette qualité, ici par > > exemple, les fonctions d'entrée/sortie. Et l'affectation se > > fera bien toujours avec des classes dérivées. > C'est certainement ce qui m'arrive. Pour être concret, j'ai > une classe abstraite message. Cette classe fournit des > services de base (canal d'adressage du message), et enveloppe > les données transmises. J'ai essayé de penser à un système > générique pour pouvoir les envoyer de la vue au contrôleur et > du modèle à la vue. Ce qui suggère (à moi, et sans connaître toutes les contraintes) l'interdiction de l'affectation dans la classe de base, avec son implémentation éventuellement dans les classes dérivées. Un autre alternatif serait de traiter le message comme si c'était un espèce d'entité, même s'il n'en est pas une logiquement, et de n'en copier que les pointeurs. (Puisque logiquement, un message ne contiendrait pas d'autres pointeurs, et donc, des cycles sont par définition impossible, on pourrait très bien se servir de boost::shared_ptr dans ce cas pour glaner les cellules, si on n'a pas d'autre glaneur de cellules.) A priori, j'imagine qu'un message, une fois initialisé, est immutable ; qu'on transmet des copies (valeur) ou des pointeurs (identité) ne change donc strictement rien, en dehors des questions de la gestion de la mémoire. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#8 |
|
Messages: n/a
Hébergeur: |
On May 7, 4:02 am, James Kanze <james.ka...@gmail.com> wrote:
> > En gros, la dérivée ne doit pas > imposer des préconditions plus contraignantes, et doit garantir > des postconditions au moins aussi fortes que la base. Je ne veux pas "hijacker" le sujet mais juste une remarque a propos de cette affirmation. C'est qquechose qu'on entend souvent et qui est dit et repete quand on parle de contrat / interface et qu'on evoque differentes implementations de ce contrat dans le temps. Par contre, c'est quelquechose qui m'a toujours plus ou moins derange (bien que je l'utilise de cette facon) car j'ai l'impression que tolerer des preconditions moins contraignantes et / ou des postconditions plus contraignantes brise le contrat d'une certaine facon. Je n'ai jamais encore pu accepter cette affirmation de but en blanc, peut etre parce qu'il me manque un element, je ne sais pas ... Si je vais dans une chaine de restos qui exige que j'apporte mon vin, et que j'ai l'habitude d'aller dans un resto de cette chaine qui a choisi d'avoir une carte de vin et donc de me lever la contrainte d'aller chercher mon vin moi meme; puis que d'un coup je change de resto et que, contre toute attente, lui refuse ... me semble que c'est moi qui ait brise le contrat initial non? LSP se trouve plutot mal en point dans ce temps la, Alex |
|
|
|
#9 |
|
Messages: n/a
Hébergeur: |
On May 8, 5:26 pm, Alexandre Abreu <wiss1...@gmail.com> wrote:
> On May 7, 4:02 am, James Kanze <james.ka...@gmail.com> wrote: > > En gros, la dérivée ne doit pas imposer des préconditions > > plus contraignantes, et doit garantir des postconditions au > > moins aussi fortes que la base. > Je ne veux pas "hijacker" le sujet mais juste une remarque a > propos de cette affirmation. C'est qquechose qu'on entend > souvent et qui est dit et repete quand on parle de contrat / > interface et qu'on evoque differentes implementations de ce > contrat dans le temps. > Par contre, c'est quelquechose qui m'a toujours plus ou moins > derange (bien que je l'utilise de cette facon) car j'ai > l'impression que tolerer des preconditions moins > contraignantes et / ou des postconditions plus contraignantes > brise le contrat d'une certaine facon. Je n'ai jamais encore > pu accepter cette affirmation de but en blanc, peut etre parce > qu'il me manque un element, je ne sais pas ... Je suis en fait un peu de cet avis moi-même, que faire des préconditions plus libérales ou des postconditions plus strictes n'est pas vraiment dans le sens de la chose, au moins quand on parle de l'aspect contractuel. Que la classe dérivée ne renvoie dans la réalité qu'un sous-ensemble des valeurs permises par la classe de base, ou qu'elle pourrait fonctionner dans les faits avec des valeurs non-permises par la classe de base en entrée, c'est tout à fait normal et même inévitable. Qu'elle en fasse la garantie, lorsque la fonction est appelée à travers l'interface de base, c'est une autre chose. L'interface de base ne garantit que la contrat de base, quelque soit la classe dérivée, et si, en tant qu'utilisateur, j'ai besoin du contrat plus libéral que pourrait m'offrir la classe dérivée, à mon avis, je dois le dire explicitement, en me servant de l'interface de la classe dérivée, par exemple avec un dynamic_cast (qui dit clairement qu'il me faut bien la dérivée, et son contrat). Du coup, quand j'implémente la programmation par contrat en C++, je n'émule pas exactement Eiffel. Les fonctions publics de la classe de base ne sont pas virtuelles ; elles vérifient le contrat de la classe de base, et renvoient à des fonctions virtuelles privées. Du coup, avec une interface de base, le client se trouve contraint à respecter rigueureusement le contrat de la classe de base. À mon avis, c'est mieux que ce que fait l'Eiffel. Mais bon, je donnais la définition de la LSP:-). (Et évidemment, une classe qui conforme aux exigeances plus strictes ci-dessus est aussi conforme à la LSP.) > Si je vais dans une chaine de restos qui exige que j'apporte > mon vin, et que j'ai l'habitude d'aller dans un resto de cette > chaine qui a choisi d'avoir une carte de vin et donc de me > lever la contrainte d'aller chercher mon vin moi meme; puis > que d'un coup je change de resto et que, contre toute attente, > lui refuse ... me semble que c'est moi qui ait brise le > contrat initial non? LSP se trouve plutot mal en point dans > ce temps la, Je ne suis pas sûr de suivre l'exemple. Il me semble ici que tu as bien une interface complètement différente : la fonction choisirDeLaCarte() manque dans le deuxième cas. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
![]() |
| Outils de la discussion | |
|
|