OVH Cloud OVH Cloud

Adaptation de paramètre à la dérivation.

10 réponses
Avatar
Vivien Parlat
Bonjour,

Je voudrais d=E9finir une classe virtuelle (comme interface pour le
polymorphisme) A, qui poss=E8de une m=E9thode de "croisement" (pour un
algo G) genre A::CroiseAvec(const A&), de mani=E8re =E0 ce que quand j'en
d=E9rive mes classes concr=E8tes, j'aie automatiquement (ou pas, mais que
=E7a soit transparent pour un pointeur sur un A) pour B (d=E9riv=E9e de A)
une m=E9thode B::CroiseAvec(const B&). Je pourrais garder const A& en
param=E8tre, mais ce ne serait pas s=FBr c=F4t=E9 v=E9rif de type, =E7a
permettrait si C est une autre sous classe de A =E0 un imprudent de
faire B::CroiseAvec(C), ce que je voudrais =E9viter.
En somme, comment dire qu'on veut, pour param=E8tre d'une m=E9thode,
quelque chose du m=EAme type que this, lors de la d=E9claration de la
classe abstraite (ou interface) ?

Merci d'avance, en esp=E9rant que ma description fut claire...

10 réponses

Avatar
Fabien LE LEZ
Ton problème ressemble à
<http://groups.google.com/group/fr.comp.lang.c++/browse_frm/thread/b16ab8d4c1f74e5e/260943a409e0ebbc#260943a409e0ebbc>
Avatar
kanze
Vivien Parlat wrote:

Je voudrais définir une classe virtuelle


Tu veux dire abstraite, je crois. (Les fonctions et l'héritage
sont virtuelle, mais non les classes.)

(comme interface pour le polymorphisme) A, qui possède une
méthode de "croisement" (pour un algo G) genre
A::CroiseAvec(const A&), de manière à ce que quand j'en dérive
mes classes concrètes, j'aie automatiquement (ou pas, mais que
ça soit transparent pour un pointeur sur un A) pour B (dérivée
de A) une méthode B::CroiseAvec(const B&).


Automatique, je ne crois pas que ce soit possible. Et même si tu
la définis à la main, elle rédéfinira pas A::CroisAvec(A
const&). C'est une autre fonction, sans rapport.

Je pourrais garder const A& en paramètre, mais ce ne serait
pas sûr côté vérif de type, ça permettrait si C est une autre
sous classe de A à un imprudent de faire B::CroiseAvec(C), ce
que je voudrais éviter.


Je ne suis pas trop sur d'avoir bien compris ce que tu veux
faire. Mais la vérification statique des types en C++ se fait
selon le type statique. C-à-d que si tu as une référence à A, et
tu appelles CroiseAvec, le compilateur va vérifier que le type
de paramètre correspond à ce qui est défini pour A::CroiseAvec.

Peut-être ce que tu cherches, c'est quelque chose du genre :

void
A::CroiseAvec( A const& autre )
{
if ( typeid( autre ) != typeid( *this ) ) {
// Traitement d'erreur...
} else {
doCroisAvec( autre ) ;
}
}

où c'est doCroiseAvec qui est virtuel, et non CroiseAvec. Mais
sans savoir exectement ce que tu cherches à faire, c'est
difficile à dire.

En somme, comment dire qu'on veut, pour paramètre d'une
méthode, quelque chose du même type que this, lors de la
déclaration de la classe abstraite (ou interface) ?


Comme le code ci-dessus. C'est une vérification dynamique, mais
ça ne peut être qu'une vérification dynamique, parce qu'elle
concerne le type dynamique, qui n'est connu que lors de
l'exécution.

Pour un type dérivé donné, évidemment, on peut ajouter la
fonction qu'il faut, du genre :

void
B::CroiseAvec( B const& autre )
{
// ...
}

void
B::doCroiseAvec( A const& autre )
{
CroiseAvec( dynamic_cast< B const& >( autre ) ) ;
}

De cette façon, quand tu connais les deux types, la fonction
correcte est appelée immédiatement. Et si tu connais le type de
l'objet (c-à-d que tu as une B& ou un B*), la fonction générique
de la classe de base est cachée -- pour y accéder, il faut
convertir le B& en A& -- et tu ne peux appeler la fonction
qu'avec quelque chose connue d'être un B.

--
James Kanze GABI Software
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

Avatar
Vivien Parlat
Bonjour,

Merci déjà pour vos réponses. J'ai cru comprendre que mon but était
peut-être peu clair.
En fait pour un algo G, j'ai défini une classe, disons, "AlgoG", qui
sera probablement un template d'une classe quelconque implémentant mon
interface IIndividu.

On aura donc, une classe Individu : public IIndividu, et un "template
<class IIndividu> class AlgoG", qui acceptera (je l'espère) toute
classe dérivant d'IIndividu; ou si cela ne marche pas, un individu qui
pourra générer les autres via la fabrique.

La classe IIndividu, abstraite donc, gère l'interface commune de
toutes les classes possibles d'individus. Généralement dans ce cas,
une responsabilité peut être traduite par une méthode de
l'interface, p.ex "un objet peut dire combien de ses semblables sont
instanciés" donnera une méthode "int nombreInstances", ou "un
individu pourra être nommé en cours d'exé" donnera "void nomme(const
char* nom)" (j'explique en détail pour rendre plus visible une
éventuelle erreur de ma part). Moi j'aimerais créer une méthode dont
la responsabilité est "un individu peut se croiser avec un individu de
la même espèce".

C'est "de la même espèce" qui pose problème; la solution
"typeid(truc)==typeid(*this)" (qui ne compile pas au passage sous mon
VC6) ne me plaît pas parce qu'elle ne traduit pas de responsabilité,
c'est juste une sécurité, comme un assert(). De même pour le test
sur un dynamic_cast.

La surcharge de croiseAvec() avec un autre type me gène, car si je
fais:
IIndividu* iind = &indiv;
iind->croiseAvec(untel);
la liaison tardive n'aura pas lieu (sauf erreur de ma part). Ce sera
IIndividu::croiseAvec qui sera appelé, & non Individu::croiseAvec. Par
ailleurs si j'implémente un croiseAvec(IIndividu&) et un
croiseAvec(Individu&), il y aura conflit, les deux acceptant un
Individu.

Enfin, par rapport à l'autre fil de discussion indiqué par Fabien LE
LEZ, le pattern Visitor ne m'inspire pas, je ne veux pas décentraliser
la fonction de croisement, au contraire.
Quant au sélecteur, je n'ai pas encore saisi s'il était applicable ou
non à mon cas.
Est-ce impossible à cause de la vérif dynamique de type ?

En espérant avoir été plus clair
Avatar
Defresne Sylvain
Vivien Parlat wrote:
Je voudrais définir une classe virtuelle (comme interface pour le
polymorphisme) A, qui possède une méthode de "croisement" (pour un
algo G) genre A::CroiseAvec(const A&), de manière à ce que quand j'en
dérive mes classes concrètes, j'aie automatiquement (ou pas, mais que
ça soit transparent pour un pointeur sur un A) pour B (dérivée de A)
une méthode B::CroiseAvec(const B&). Je pourrais garder const A& en
paramètre, mais ce ne serait pas sûr côté vérif de type, ça
permettrait si C est une autre sous classe de A à un imprudent de
faire B::CroiseAvec(C), ce que je voudrais éviter.
En somme, comment dire qu'on veut, pour paramètre d'une méthode,
quelque chose du même type que this, lors de la déclaration de la
classe abstraite (ou interface) ?


Si je comprends bien, tu veux savoir si le C++ supporte la définition de
méthodes covariante à la manière d'Eiffel. Je ne crois pas que ce soit
possible de le vérifier à la compilation, et je ne pense pas que C++ le
supporte.

Pour info : http://www.adahome.com/FAQ/programming.html#covariance
--
Defresne Sylvain

Avatar
loic.actarus.joly

C'est "de la même espèce" qui pose problème; la solution
"typeid(truc)==typeid(*this)" (qui ne compile pas au passage sous mon
VC6) ne me plaît pas parce qu'elle ne traduit pas de responsabilité,



Je ne comprends pas trop ce que tu veux dire ici.


La surcharge de croiseAvec() avec un autre type me gène, car si je
fais:
IIndividu* iind = &indiv;
iind->croiseAvec(untel);
la liaison tardive n'aura pas lieu (sauf erreur de ma part). Ce sera
IIndividu::croiseAvec qui sera appelé, & non Individu::croiseAvec. Par
ailleurs si j'implémente un croiseAvec(IIndividu&) et un
croiseAvec(Individu&), il y aura conflit, les deux acceptant un
Individu.



Si j'ai bien compris, tu veux des fonctions virtuelles par rapport à 2
arguments bool estFertile(IIndividu , IIndividu). Ce n'est pas possible
directement en C++. Il y a des approximations. Si tu as accès à More
effective C++, l'item 31 parle de ce sujet.

--
Loïc

Avatar
kanze
Vivien Parlat wrote:

[...]
C'est "de la même espèce" qui pose problème; la solution
"typeid(truc)==typeid(*this)" (qui ne compile pas au passage
sous mon VC6) ne me plaît pas parce qu'elle ne traduit pas de
responsabilité, c'est juste une sécurité, comme un assert().
De même pour le test sur un dynamic_cast.


Ou bien, tu connais le type lors de la compilation, et tu
traites avec ce type, et il n'y a pas de problème, le
compilateur fait toutes les vérifications nécessaire. Ou bien,
tu ne le connais pas, et il faut que tu sois en mesure de faire
des vérifications dynamiques.

La surcharge de croiseAvec() avec un autre type me gène, car
si je fais:
IIndividu* iind = &indiv;
iind->croiseAvec(untel);
la liaison tardive n'aura pas lieu (sauf erreur de ma part).


Est-ce que tu veux le polymorphisme, ou est-ce que tu ne le veux
pas ? Si tu le veux, il faut que tu puisses traiter le cas où
IIndividu n'a pas le bon type dynamique, à l'exécution. Si tu ne
le veux pas, je ne vois pas où est le problème en ce que chaque
classe dérivée a sa propre fonction.

Note bien que même à l'intérieur d'un programme, on peut bien le
vouloir à certains endroits, et pas à d'autres. C'est donc tout
à fait normal que la classe de base ait une fonction
croiseAvec(Base*) qui se comporte de façon polymorphique, avec
éventuellement des erreurs lors de l'exécution si on essaie un
croisement interdit, et que les classes dérivées aient des
fonctions croiseAvec(Derived*) qui ne sont pas polymorphique.

Ce sera IIndividu::croiseAvec qui sera appelé, & non
Individu::croiseAvec. Par ailleurs si j'implémente un
croiseAvec(IIndividu&) et un croiseAvec(Individu&), il y aura
conflit, les deux acceptant un Individu.


D'abord, je ne vois croiseAvec(Base&) que dans la classe de
base. Et je ne le vois pas virtuelle, mais qu'il appelle une
fonction virtuelle privée. Du genre :

class Base
{
public:
ErrorCode croiseAvec( Base& autre )
{
// Mais on pourrait très bien aussi laisser
// la décision si c'est légal à la classe
// dérivée. Question de conception.
return typeid( autre ) == typeid( *this )
? doCroiseAvec( autre )
: illegalTypeError ;
}

private:
virtual ErrorCode doCroiseAvec( Base& autre ) = 0 ;
} ;

class Derived : public Base
{
public:
ErrorCode croiseAvec( Derived& autre ) ;

private:
ErrorCode doCroiseAvec( Base& autre )
{
assert( typeid( *this ) == typeid( autre ) ) ;
return croiseAvec( static_cast< Derived& >( autre )) ;
}
} ;

Du coup, si tu connais le type (et donc, que tu as un Derived*
ou un Derived&), tu va appeler Derived::croiseAvec directement,
et si tu essaies d'appeler croiseAvec avec un autre type, le
compilateur se plaint. Et si tu ne connais pas le type, c'est
qu'il risque de varier lors de l'exécution, et tu ne peux
effectuer les vérifications qu'à l'exécution.

Et en passant, il n'y a aucun problème avec deux fonctions du
même nom qui prend des paramètres des types différents. Les
règles de résolution du surcharge entre en jeux -- en
particulier, on va préférer la fonction qui correspond
exactement à celle qui exige une conversion Derived& vers Base&.
(Mais ça n'entre pas en jeu ici, parce qu'il n'y a jamais qu'une
croiseAvec en jeux.)

Enfin, par rapport à l'autre fil de discussion indiqué par
Fabien LE LEZ, le pattern Visitor ne m'inspire pas, je ne veux
pas décentraliser la fonction de croisement, au contraire.


Dans ce cas-ci, je ne crois pas non plus qu'il soit nécessaire.
Dans le cas général, en revanche... Le C++ n'a pas de double
dispatch au niveau du langage, et le modèle visiteur est sans
doute la façon la plus simple à le simuler.

Quant au sélecteur, je n'ai pas encore saisi s'il était
applicable ou non à mon cas. Est-ce impossible à cause de la
vérif dynamique de type ?

En espérant avoir été plus clair


Je n'ai toujours pas bien saisi si tu sais le type réel lors de
la compilation ou non.

--
James Kanze GABI Software
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

Avatar
Vivien Parlat

Vivien Parlat wrote:

[...]
C'est "de la même espèce" qui pose problème; la solution
"typeid(truc)==typeid(*this)" (qui ne compile pas au passage
sous mon VC6) ne me plaît pas parce qu'elle ne traduit pas de
responsabilité, c'est juste une sécurité, comme un assert().
De même pour le test sur un dynamic_cast.


Ou bien, tu connais le type lors de la compilation, et tu
traites avec ce type, et il n'y a pas de problème, le
compilateur fait toutes les vérifications nécessaire. Ou bien,
tu ne le connais pas, et il faut que tu sois en mesure de faire
des vérifications dynamiques.


'Moi' je le connais, concrètement AlgoG n'utilisera que des valeurs
provenant soit d'une fabrique, soit du template. Donc un algoG
n'utilisera qu'un type concret en même temps. La vérification est
censée éviter qu'un algoG mal implémenté passe, même si l'erreur
est peu probable, un paramètre à croiseAutre dérivant de la même
interface.

La surcharge de croiseAvec() avec un autre type me gène, car
si je fais:
IIndividu* iind = &indiv;
iind->croiseAvec(untel);
la liaison tardive n'aura pas lieu (sauf erreur de ma part).


Est-ce que tu veux le polymorphisme, ou est-ce que tu ne le veux
pas ? Si tu le veux, il faut que tu puisses traiter le cas où
IIndividu n'a pas le bon type dynamique, à l'exécution. Si tu ne
le veux pas, je ne vois pas où est le problème en ce que chaque
classe dérivée a sa propre fonction.


J'avoue que vu comme ça... le traitement avec typeid (quand VC6 ne
m'affichera plus de message d'erreur sur l'égalité de 2 typeid()) et
la gestion de l'erreur semblent la solution la plus raisonnable.

Ce sera IIndividu::croiseAvec qui sera appelé, & non
Individu::croiseAvec. Par ailleurs si j'implémente un
croiseAvec(IIndividu&) et un croiseAvec(Individu&), il y aura
conflit, les deux acceptant un Individu.


D'abord, je ne vois croiseAvec(Base&) que dans la classe de
base. Et je ne le vois pas virtuelle, mais qu'il appelle une
fonction virtuelle privée. Du genre :

class Base
{
public:
ErrorCode croiseAvec( Base& autre )
{
// Mais on pourrait très bien aussi laisser
// la décision si c'est légal à la classe
// dérivée. Question de conception.
return typeid( autre ) == typeid( *this )
? doCroiseAvec( autre )
: illegalTypeError ;
}

private:
virtual ErrorCode doCroiseAvec( Base& autre ) = 0 ;
} ;

class Derived : public Base
{
public:
ErrorCode croiseAvec( Derived& autre ) ;

private:
ErrorCode doCroiseAvec( Base& autre )
{
assert( typeid( *this ) == typeid( autre ) ) ;
return croiseAvec( static_cast< Derived& >( autre )) ;
}
} ;

Du coup, si tu connais le type (et donc, que tu as un Derived*
ou un Derived&), tu va appeler Derived::croiseAvec directement,
et si tu essaies d'appeler croiseAvec avec un autre type, le
compilateur se plaint. Et si tu ne connais pas le type, c'est
qu'il risque de varier lors de l'exécution, et tu ne peux
effectuer les vérifications qu'à l'exécution.

Et en passant, il n'y a aucun problème avec deux fonctions du
même nom qui prend des paramètres des types différents. Les
règles de résolution du surcharge entre en jeux -- en
particulier, on va préférer la fonction qui correspond
exactement à celle qui exige une conversion Derived& vers Base&.
(Mais ça n'entre pas en jeu ici, parce qu'il n'y a jamais qu'une
croiseAvec en jeux.)


Effectivement je ne voyais pas ça comme ça. J'imaginais disposer dans
Derived des deux méthodes croiseAvec, l'une prenant un Base&, l'autre
un Derived&. Par contre j'ignorais que ça pouvait être résolu
correctement lorsque les deux types faisant objet de la surcharge
étaient parents.

Enfin, par rapport à l'autre fil de discussion indiqué par
Fabien LE LEZ, le pattern Visitor ne m'inspire pas, je ne veux
pas décentraliser la fonction de croisement, au contraire.


Dans ce cas-ci, je ne crois pas non plus qu'il soit nécessaire.
Dans le cas général, en revanche... Le C++ n'a pas de double
dispatch au niveau du langage, et le modèle visiteur est sans
doute la façon la plus simple à le simuler.


Je ne connais pas le double dispatch. Est-ce que ça permet d'écrire
qqch genre "(A,B).croiseAvec() (qui se traduirait par deux références
cachées à la compil contre une en temps normal) ? Dans l'idéal
j'aurais aimé pouvoir écrire ce genre de choses, même si je ne sais
pas si c possible.

Quant au sélecteur, je n'ai pas encore saisi s'il était
applicable ou non à mon cas. Est-ce impossible à cause de la
vérif dynamique de type ?


Je n'ai toujours pas bien saisi si tu sais le type réel lors de
la compilation ou non.
Moi oui, AlgoG manipule une interface (pour ce qui est prévu du

moins), afin de pouvoir s'adapter à un autre problème, juste en
dérivant Base, et en passant cette dérivée à une instance d'algoG.

Je pense que je vais adopter ta solution, une fois résolu le pbm de
l'égalité entre typeid; si j'ai bien compris typeid demande de faire
appel à l'option RTTI (dans VC). Est-ce que le source reste portable,
ie est-ce que tous les compilos l'accepteront ?


Avatar
Vivien Parlat


C'est "de la même espèce" qui pose problème; la solution
"typeid(truc)==typeid(*this)" (qui ne compile pas au passage sous m on
VC6) ne me plaît pas parce qu'elle ne traduit pas de responsabilité,


Je ne comprends pas trop ce que tu veux dire ici.


Disons qu'avec ce que j'ai expliqué sur les responsabilités, le test
de type n'apparaît pas dans l'interface de la classe, c'est "codé en
dur", ça ne fait pas partie de la classe elle-même si on ne
l'implémente pas comme ça.


La surcharge de croiseAvec() avec un autre type me gène, car si je
fais:
IIndividu* iind = &indiv;
iind->croiseAvec(untel);
la liaison tardive n'aura pas lieu (sauf erreur de ma part). Ce sera
IIndividu::croiseAvec qui sera appelé, & non Individu::croiseAvec. Par
ailleurs si j'implémente un croiseAvec(IIndividu&) et un
croiseAvec(Individu&), il y aura conflit, les deux acceptant un
Individu.



Si j'ai bien compris, tu veux des fonctions virtuelles par rapport à 2
arguments bool estFertile(IIndividu , IIndividu). Ce n'est pas possible
directement en C++. Il y a des approximations. Si tu as accès à More
effective C++, l'item 31 parle de ce sujet.


D'après google il s'agit d'un bouquin... je n'y ai pas accès (du
moins pas encore), peux tu me dire (en gros résumé) en quoi consiste
leur approximation ?


Avatar
loic.actarus.joly

Je pense que je vais adopter ta solution, une fois résolu le pbm de
l'égalité entre typeid; si j'ai bien compris typeid demande de faire
appel à l'option RTTI (dans VC). Est-ce que le source reste portable,
ie est-ce que tous les compilos l'accepteront ?


Oui, c'est tout à fait standard. Et c'est implémenté sur tous les
compilateurs un minimum récents. Pour info, dans ses derniers
compilateurs, Microsoft a (enfin) décidé que l'option par défaut est
d'avoir le RTTI actif.


--
Loïc

Avatar
kanze
Vivien Parlat wrote:

Enfin, par rapport à l'autre fil de discussion indiqué par
Fabien LE LEZ, le pattern Visitor ne m'inspire pas, je ne
veux pas décentraliser la fonction de croisement, au
contraire.


Dans ce cas-ci, je ne crois pas non plus qu'il soit
nécessaire. Dans le cas général, en revanche... Le C++ n'a
pas de double dispatch au niveau du langage, et le modèle
visiteur est sans doute la façon la plus simple à le
simuler.


Je ne connais pas le double dispatch. Est-ce que ça permet
d'écrire qqch genre "(A,B).croiseAvec() (qui se traduirait par
deux références cachées à la compil contre une en temps
normal) ? Dans l'idéal j'aurais aimé pouvoir écrire ce genre
de choses, même si je ne sais pas si c possible.


Le double dispatch, littéralement, c'est l'aiguillage dynamique
sur deux « paramtres ». Laissons pour un instant à côté la
syntaxe particulière de C++, a.f( b ) (ou a est dans les fait un
paramètre aussi de f), et considérons f( a, b ). En C++, le seul
aiguillage dynamique possible, c'est sur a. En d'autres
langages, je crois, on pourrait très bien aiguiller sur b, ou
sur tous les deux. Quand un langage supporte l'aiguillage sur
plus d'un paramtre, on parle de « double dispatch » (bien que si
on permet deux, j'imagine qu'on en permet plus aussi).

Dans le cas de C++, l'aiguillage est étroitement lié à la classe
même. Ce qui rend la génération d'une implémentation rapide
assez simple. Et qui n'est pas sans avantages en ce qui concerne
la lisibilité du code non plus. On pourrait bien imaginer un
langage où il n'y a pas de fonctions membre, que des amis, et
que l'aiguillage pourrait se faire sur n'importe ou sur tous les
paramètres. Ça serait beaucoup plus difficile à implémenter, en
revanche. Et j'avoue qu'il me plaît aussi que dans a.f(b), je
sais où trouver la declaration définitive de f, et que la forme
de l'écriture signale bien de quoi la fonction définitivement
sélectionnée pourrait dépendre.

[...]
Je pense que je vais adopter ta solution, une fois résolu le
pbm de l'égalité entre typeid; si j'ai bien compris typeid
demande de faire appel à l'option RTTI (dans VC). Est-ce que
le source reste portable, ie est-ce que tous les compilos
l'accepteront ?


Aujourd'hui, c'est à espérer. La RTTI fait partie de la norme,
et la norme est officielle depuis déjà sept ans. Et en ce qui
concerne les options... tous les compilateurs, à ma
connaissance, ont besoin des options pour être conforme. Je sais
qu'avec VC++ 6.0, il me fallait au moins /GX /GR /vmg /Tp; avec
g++ ou Sun CC, j'ai aussi une liste assez impressionnante.

--
James Kanze GABI Software
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