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

10 réponses

1 2
Avatar
Arnaud Meurgues
Olivier Miakinen wrote:

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


Il est probable que le problème soit résolu par un héritage virtuel.

L'héritage virtuel permet, lorsqu'on hérite de deux objet ayant un
parent commun que ce parent soit considéré comme un ancêtre unique et
non comme deux ancêtres distincts.

Si un classe hérite de deux fois le même ancêtre, alors il voit deux
fois les mêmes fonctions (l'une de la branche gauche, l'autre de la
branche droite) et l'appel d'une fonction de cet ancêtre dupliqué est
ambigü tant qu'on n'a pas choisi explicitement quelle fonction choisir.

Si vous utilisez l'héritage virtuel, alors l'ancêtre (CWnd) venant de la
branche gauche (CBaseUI) serait le même que l'ancêtre venant de la
branche droite (CEdit).

Malheureusement, il faudrait pour que cela fonctionne que CEdit (et les
autres) héritent virtuellement de CWnd, ce qui n'est probablement pas le
cas. Si jamais c'est le cas, cela pourrait résoudre votre problème.

Si ce n'est pas le cas, alors dériver deux fois d'une même classe n'est
pas une bonne idée : cela voudrait dire qu'un objet CTextUI (par
exemple) serait à la fois une CWnd et une autre CWnd, ce qui n'est
probablement pas la sémantique que vous désiriez.

--
Arnaud

Avatar
Jean-Marc Bourguet
Olivier Miakinen <om+ writes:

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


Je ne commenterais pas sur la structure initiale,

CBaseUI::GetWindowRect(CRect* rect) {
dynamic_cast<CWnd&>(*this).GetWindowRect(rect);
}

dynamic_cast<CWnd&> plutot que dynamic_cast<CWnd*> pour avoir une
exception plutot que dereferencer un pointeur nul si la classe la plus
derivee n'herite pas aussi de CWnd. Naturellement, il faut aussi
avoir des membres virtuels ou il faut pour que dynamic_cast
fonctionne.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Olivier Miakinen

Il est probable que le problème soit résolu par un héritage virtuel.

[...]

Malheureusement, il faudrait pour que cela fonctionne que CEdit (et les
autres) héritent virtuellement de CWnd, ce qui n'est probablement pas le
cas. Si jamais c'est le cas, cela pourrait résoudre votre problème.


Je viens de vérifier (au hasard pour CDateTimeCtrl), et en effet
ce n'est pas le cas. Merci pour la piste en tout cas, même si
malheureusement elle n'aboutit pas.

Si ce n'est pas le cas, alors dériver deux fois d'une même classe n'est
pas une bonne idée : cela voudrait dire qu'un objet CTextUI (par
exemple) serait à la fois une CWnd et une autre CWnd, ce qui n'est
probablement pas la sémantique que vous désiriez.


Oui, je pensais bien que ça ne marcherait pas, et je n'ai pas été
surpris par les messages d'erreur du compilateur.

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

Avatar
Olivier Miakinen

Je ne commenterai pas sur la structure initiale,


Je crois qu'il vaut mieux, sauf si bien sûr c'est nécessaire pour la
remplacer par un code meilleur.

CBaseUI::GetWindowRect(CRect* rect) {
dynamic_cast<CWnd&>(*this).GetWindowRect(rect);
}


Je ne connaissais pas, mais cela m'a permis de trouver ceci :
<http://www.developpez.com/c/megacours/x4009.html>.

... dynamic_cast, static_cast, const_cast et reinterpret_cast. Eh bien,
j'ai de quoi faire avec ça ! ;-)

dynamic_cast<CWnd&> plutot que dynamic_cast<CWnd*> pour avoir une
exception plutot que dereferencer un pointeur nul si la classe la plus
derivee n'herite pas aussi de CWnd.


Quoique le cas ne doive en principe pas se produire, je vais suivre ce
conseil. Je suppose que si j'avais opté pour le pointeur ç'aurait été :
dynamic_cast<CWnd*>(this)->GetWindowRect(rect);

Naturellement, il faut aussi avoir des membres virtuels où il faut
pour que dynamic_cast fonctionne.


Lesquels ? La méthode GetWindowRect() dans CWnd ? La même mais dans
CBaseUI ? Autre ?

--
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
Jean-Marc Bourguet
Olivier Miakinen <om+ writes:

dynamic_cast<CWnd&> plutot que dynamic_cast<CWnd*> pour avoir une
exception plutot que dereferencer un pointeur nul si la classe la plus
derivee n'herite pas aussi de CWnd.


Quoique le cas ne doive en principe pas se produire, je vais suivre ce
conseil. Je suppose que si j'avais opté pour le pointeur ç'aurait été :
dynamic_cast<CWnd*>(this)->GetWindowRect(rect);


Gagne. Tu peux aussi opter pour cela mais en verifiant que le
pointeur n'est pas nul et en faisant quelque chose d'approprie dans ce
cas.

Naturellement, il faut aussi avoir des membres virtuels où il faut
pour que dynamic_cast fonctionne.


Lesquels ? La méthode GetWindowRect() dans CWnd ? La même mais dans
CBaseUI ? Autre ?


CBaseUI doit avoir au moins un membre virtuel. Si j'ai bonne memoire
-- quelqu'un peut-il confirmer, je n'ai pas de bonne reference sous la
main ce qui etait la raison pour laquelle j'ai ete vague -- il n'y a
pas de condition sur CWnd.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Olivier Miakinen

Quoique le cas ne doive en principe pas se produire, je vais suivre ce
conseil. Je suppose que si j'avais opté pour le pointeur ç'aurait été :
dynamic_cast<CWnd*>(this)->GetWindowRect(rect);


Gagne. Tu peux aussi opter pour cela mais en verifiant que le
pointeur n'est pas nul et en faisant quelque chose d'approprie dans ce
cas.


Je vais opter pour la simplicité, à savoir ta solution initiale.

CBaseUI doit avoir au moins un membre virtuel.


Ça c'est bon. Il a une bonne demi-douzaine de méthodes virtuelles, à
commencer par le destructeur.

Merci pour tout !

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

[...] Si j'ai bonne memoire -- quelqu'un peut-il confirmer, je n'ai
pas de bonne reference sous la main ce qui etait la raison pour
laquelle j'ai ete vague -- il n'y a pas de condition sur CWnd.


Il se trouve qu'il y a aussi des méthodes virtuelles dans CWnd, alors
je ne peux pas confirmer que cela fonctionnerait aussi s'il n'y en
avait pas.

En tout cas ton code fonctionne au poil. Encore merci !

--
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 22/05/2006 15:53:

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.


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

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 schéma me parait consistant - vu l'arborescence de pseudo-classes des
MFC - et CBaseUI ne pouvait en effet pas hériter de CWnd car les MFC
ignore l'héritage virtuel (on peut aussi prévilégier un schéma sans les
MFC, WTL par exemple).

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

donc l'accès à GetW_Rect depuis un CBaseUI* est possible.

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);
}


c'est tordu, ici GetWindowRect est apporté visiblement par CEdit, son
implémentation dévirtualise CBaseUI (s'il définit la méthode virtuelle
pure) et cache la méthode de CEdit (reçu de CWnd) ... pourquoi faire
simple ?...

l'accès direct à GetW_Rect depuis un CTextUI (C??UI) était possible.

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

pour les comportements (messages GUI) identiques à tous les composants
graphiques windows, utiliser alors directement les API win32 (user32)
pour coder une seule fois le service, par exemple:

bool CBaseUI::getRect(CRect& rect){
return (::GetWindowRect(getHandle(), rect) == TRUE);
}

notez que ces accesseurs ne pourront pas être const (sauf à déclarer
hWnd mutable), les API win32 étant également un peu faible sur ce point.

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.

Sylvain.

Avatar
Frederic Lachasse
"Olivier Miakinen" <om+ wrote in message
news:e4sfn1$2thb$
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 ?


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> { ... }

--
Frédéric Lachasse - ECP86

Avatar
kanze
Sylvain wrote:
Olivier Miakinen wrote on 22/05/2006 15:53:

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 schéma me parait consistant - vu l'arborescence de pseudo-classes des
MFC - et CBaseUI ne pouvait en effet pas hériter de CWnd car les MFC
ignore l'héritage virtuel (on peut aussi prévilégier un schéma sa ns les
MFC, WTL par exemple).

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


Attention ! Cette ligne ne fait pas du tout ce qui a été
démandée. D'abord, évidemment, il ne doit pas compiler ; *this
n'est pas un paramètre légal de reinterpret_cast. Mais je
suppose que c'est un typo, et que c'est simplement this qu'on a
voulu dire. Mais alors, qu'est-ce que signifie le
reinterpret_cast dans ce cas-ci ? En fait, il dit au compilateur
que l'adresse this est en fait l'adresse d'un CWnd. Ce qui est
prèsque sûrement faux dans l'hièrarchie en question. Surtout,
reinterpret_cast ne démande pas de conversion ; il dit au
compilateur que moi, le programmeur, sait plus que le
compilateur ici.

En général, il faut se méfier de toute utilisation de
reinterpret_cast, et surtout, il ne faut jamais s'en servir pour
la navigation à l'intérieur d'une hièrarchie. (La motivation de
l'introduction des nouveaux casts, c'était précisement de
pouvoir distinguer entre la naviagion à l'intérieur d'une
hièrarchie -- static_cast -- et le « type punning ».)

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


Le test n'est pas nécessaire -- un reinterpret_cast de this ne
peut jamais renvoyer un pointeur null. En revanche, l'appel de
la fonction risque de poser pas mal de problèmes.

}

donc l'accès à GetW_Rect depuis un CBaseUI* est possible.

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);
}


c'est tordu, ici GetWindowRect est apporté visiblement par
CEdit, son implémentation dévirtualise CBaseUI (s'il définit
la méthode virtuelle pure) et cache la méthode de CEdit (reçu
de CWnd) ... pourquoi faire simple ?...


Sans dynamic_cast, c'est la seule solution possible.

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