OVH Cloud OVH Cloud

Question sur le polymorphisme

16 réponses
Avatar
Olivier Miakinen
Bonjour,

Je reprends un code C++ (sur Windows avec MFC) que je dois étendre. En
particulier je suis tombé sur un truc qui ne me semble pas très propre,
et je voudrais savoir comment l'améliorer.


Il y a une classe de base, mettons CBaseUI, qui est dérivée plusieurs
fois. Chacune des classe dérivées dérive aussi d'une sous-classe du CWnd
des Microsoft Fondation Classes :

class CTextUI : public CBaseUI, public CEdit;
class CPasswordUI : public CBaseUI, public CEdit;
class CEnumUI : public CBaseUI, public CComboBox;
class CDateUI : public CBaseUI, public CDateTimeCtrl;
etc.

Toutes les classes CEdit, CComboBox, CDateTimeCtrl, CButton, etc., sont
dérivées de CWnd, ce qui fait que les classes dérivées de CBaseUI sont
elles-mêmes dérivées de CWnd -- mais pas CBaseUI elle-même.

Le besoin est, à partir d'un pointeur CBaseUI * pointant vers l'une des
sous-classes, d'appeler la méthode GetWindowRect (définie dans CWnd).
l'implémentation actuelle passe par une fonction virtuelle dans CBaseUI,
définie dans chacune des sous-classes par :

void CTextUI::GetWindowRect(CRect *rect) // idem CPasswordUI, etc.
{
((CWnd *)this)->GetWindowRect(rect);
}

Ma question principale est : y a-t-il un moyen pour éviter de redéfinir
GetWindowRect vingt fois, avec vingt fois le même code ? J'ai essayé de
dériver CBaseUI de CWnd, mais alors j'obtiens des tas d'erreurs de
compilation « CTextUI::uneméthode is ambiguous ».

Par ailleurs, sans rien changer d'autre, puis-je au moins remplacer :
((CWnd *)this)->GetWindowRect(rect);
par :
CWnd::GetWindowRect(rect);
dans chaque classe dérivée ?


Cordialement,
--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)

6 réponses

1 2
Avatar
Sylvain
kanze wrote on 23/05/2006 08:57:

bool CBaseUI::getRect(RECT& rect){
CWnd* wnd = reinterpret_cast<CWnd*>(*this);


Cette ligne ne fait pas du tout ce qui a été démandé

return (wnd) ? (wnd->GetWindowRect(&rect) == TRUE) : false;


Le test n'est pas nécessaire -- [...]


merci d'avoir corrigé James c'est bien dynamic_cast que je voulais
écrire - va savoir pourquoi j'ai tapé reinterpret ... mis à part 40°
depuis 4 jours.

Sylvain.


Avatar
Olivier Miakinen
Bonjour, et pardon d'avoir mis si longtemps à répondre.


c'est pas tant sur le polymorphisme que sur les contradictions et
erreurs des MFC dans ce cas.


;-)

C'est bien possible, mais je n'ai pas les moyens d'y changer grand chose
alors je dois faire avec (il n'est bien évidemment pas question que je
remplace les MFC par autre chose dans ce projet qui contient près de
2000 fichiers).

Le besoin est, à partir d'un pointeur CBaseUI * pointant vers l'une des
sous-classes, d'appeler la méthode GetWindowRect (définie dans CWnd).


ben y-a-qu'à alors !

la virtualité est impossible mais un cast vers une classe fille par
voies détournées est possible:

bool CBaseUI::getRect(RECT& rect){
CWnd* wnd = reinterpret_cast<CWnd*>(*this);
return (wnd) ? (wnd->GetWindowRect(&rect) == TRUE) : false;
}


J'ai bien noté que tu voulais écrire dynamic_cast à la place de
reinterpret_cast, mais finalement j'ai opté pour la proposition de
Jean-Marc Bourguet.

pour clarifier ce qui est commun à tous les composants visuels, vous
pouvez commencer par introduire un HWND (disons hWnd) dans la classe
CBaseUI en le mettant à null dans le constructeur.

puis prévoyez un méthode protégée telle:

HWND CBaseUI::getHandle(){
if (hWnd == null){
CWnd* wnd = reinterpret_cast<CWnd*>(this);
if (wnd)
hWnd = wnd->GetSafeHwnd();
}
return hWnd;
}
(la création du handle graphique est toujours différée par rapport au
constructeur - c'est fait dans ::CreateWindow[Ex])


Je ne connaissais pas ce handle. Merci de m'en avoir parlé : je ne m'en
servirai probablement pas ici, mais cela pourrait m'être utile ailleurs.

Par ailleurs, sans rien changer d'autre, puis-je au moins remplacer :
((CWnd *)this)->GetWindowRect(rect);
par :
CWnd::GetWindowRect(rect);
dans chaque classe dérivée ?


non, GetWindowRect n'est pas une méthode de classe mais d'instance, qui
utilise la donnée membre publique (ben si pourquoi?) 'm_hWnd' de CWnd
pour appeler la méthode globale comme nous l'avons fait ci-avant.


Je ne comprends pas ce qui gêne, là. Le CTextUI::GetWindowRect() et
les autres sont aussi des méthodes d'instance, et il me semblait que
l'instance était passée automatiquement dans ce cas là.

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)


Avatar
Olivier Miakinen

Ma question principale est : y a-t-il un moyen pour éviter de redéfinir
GetWindowRect vingt fois, avec vingt fois le même code ?


Une solution est de faire redéfinir 20 fois la fonction par un template:

template<class T> class CTBaseUI<T> : public T
{
public:
void GetWindowRect(CRect* rect)
{
T::GetWindowRect(rect);
}
}

class CTextUI : public CTBaseUI<CEdit> { ... }
class CPasswordUI : public CTBaseUI<CEdit> { ... }
class CEnumUI : public CTBaseUI<CComboBox> { ... }
class CDateUI : public CTBaseUI<CDateTimeCtrl> { ... }


Avec cette solution, est-il toujours possible d'avoir des méthodes
spécifiques à chaque classe ? Parce que bien évidemment je ne fais pas
le même traitement selon que l'on a un texte en clair, un mot de passe
ou une date.

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)


Avatar
Sylvain
Olivier Miakinen wrote on 29/05/2006 18:49:

Par ailleurs, sans rien changer d'autre, puis-je au moins remplacer :
((CWnd *)this)->GetWindowRect(rect);
par :
CWnd::GetWindowRect(rect);
dans chaque classe dérivée ?
non, GetWindowRect n'est pas une méthode de classe mais d'instance [..]



Je ne comprends pas ce qui gêne, là. Le CTextUI::GetWindowRect() et
les autres sont aussi des méthodes d'instance, et il me semblait que
l'instance était passée automatiquement dans ce cas là.


c'était une autre annerie, lisant comme un appel à une méthode statique
ce qui était - fort justement - un appel "contextualisé".
(on devrait s'abstenir en cas de grosse fièvre!)

Sylvain.



Avatar
Olivier Miakinen
Le 29/05/2006 21:50, Sylvain m'a répondu :

[...]

Je ne comprends pas ce qui gêne, là. [...]


c'était une autre annerie, lisant comme un appel à une méthode statique
ce qui était - fort justement - un appel "contextualisé".


Merci de la confirmation.

(on devrait s'abstenir en cas de grosse fièvre!)


(en même temps, comme on n'a rien d'autre à faire, je comprends que la
tentation soit forte d'aller traîner dans les news) ;-)

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)


Avatar
kanze
Sylvain wrote:
kanze wrote on 30/05/2006 09:38:


[...]
normalement, les classes dont le nom commence par
un C majuscule sont celles fournies par Microsoft


ah bon, y'a un copyright exclusif ???


Non, c'est juste la convention que Microsoft a établi. Si tu
veux travailler avec des classes en provenance de Microsoft, le
respecter rend ton code plus lisible, et réduire la risque des
conflits de nom.

Aujourd'hui, a priori, la solution préférée, c'est les
namespace. Mais il y a beaucoup de bibliothèque qui ont vu la
jour avant les namespaces. Les bonnes, et même la plupart des
moins bonnes, se servent des préfixes de nommage pour éviter les
collisions : chez RogueWave, par exemple, les noms de classe
commencent tous par RW. Microsoft a choisi, pour des raisons
qui me dépasse, C.

un certain nombre de langages plus récents n'ont même
pas daigner supporter l'héritage multiple, si essentiel
dans ce genre de cas


hmm, critique à peine masqué à AWT et Swing ?? ;)


Pas à Swing, en tout cas. Il a fait ce qu'il a pu avec ce que le
langage permet. Mais les problèmes qu'il a par rapport à AWT
sont un bon exemple du problème : javax.swing.JComponent est à
la fois un composant et une container, parce qu'on n'avait pas
la possibilité de créer un javax.swing.JComponent qui dérive de
java.awt.Component et un javax.swing.JContainer qui dérive et de
javax.swing.JComponent et de java.awt.Container.

Ou est-ce que tu le considères bien que je peux appeler
add(Component) sur un JButton, bien que le composant ne serait
pas correctement affiché ?

dans ma petite expérience des GUI (en mode texte en Pascal
object, mode graphique en Java et en C++ (sur système
graphique Win32, X11 et TCL)) l'héritage multiple n'a jamais
été indispensable, et je ne l'ai jamais utilisé (par contre
les interfaces sont omniprésentes).


Il existe toujours des work-arounds. Mais l'héritage multiple
est bien utile chaque fois que tu as une hièrarchie fournie par
ailleurs, avec les possibilités de customisation à chaque
niveau. Customisation au moyen du modèle template, s'entend ;
mais c'est la façon la plus répandue de supporter la
customisation dans les hièrarchies des interfaces graphiques.
(Quand on a beaucoup de points indépendants de customisation, le
modèle stratégie devient vite lourd.)

--
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


1 2