OVH Cloud OVH Cloud

Methodes virtuelles / Heritage multiple

16 réponses
Avatar
BOUCNIAUX Benjamin
Bonjour tout le monde.

Avant d'attaquer sur le probleme, je vais exposer quelques faits:

Je suis en train de développer une UI pour un jeu d'echecs, qui utilise
le framework wxwidgets. En gros, ce jeu est constitué d'un controlleur
principal, puis de plusieurs moteurs/controlleurs en arriere plan, dont un
moteur réseau. A savoir également qu'une CLI est intégrée.

J'ai donc un problème lorsque j'attend des données provenant du réseau:
l'appel à recv() est bloquant, ce qui provoque un arret du
rafraichissement du programme.

J'ai donc envisagé une solution, qui consiste à "implémenter une
interface" (ie hériter d'une classe avec des méthodes virtuelles).
Bien évidemment, mes UI/CLI hériteraient de cette interface, qui le
forcerait à implémenter une méthode 'refresh()', dont le nom veut tout
dire. De cette maniere, je pourrai faire des appels non bloquants à
recv(), en bouclant tant que rien n'arrive, et en rafraichissant
l'interface à chaque tour de boucle.

Je me retrouve donc avec une UI contenant cette methode virtuelle, la CLI
qui fait de même, et une classe (l'interface) qui aura la méthode

virtuelle pure.

Nous avons donc:

- La classe IUI qui correspondra à l'interface à implémenter.

- La classe de l'UI sera appelée ChessFrame, et heritera de wxFrame,
ainsi que de IUI.

- La classe de la CLI qui sera appelée CLI :p, et héritera également de
IUI.

Je passerai donc, si nécessaire, un pointeur sur un objet de type IUI a
un controlleur, qui appellera la methode refresh() quand il le faudra.

Jusque là, rien de spécial.

Mais je viens de la coder, et bizarrement, lorsque j'effectue les appels,
j'ai l'impression qu'aucune méthode n'est réellement appelée: j'ai mit
un message de trace dans chacune d'elles, de manière à savoir si l'appel
était le bon, mais rien ne s'affiche.

Le code de l'application fait pour le moment a peu pres 6000 lignes, et
j'aurai du mal à tout paster ici.
Cependant, je met quelques morceaux de code qui pourraient être
succeptibles d'éclairer mes propos (certaines portions seront surement
snippées pour ne pas prendre trop de place):


/* IUI.h */
/***********************************************************/
#ifndef IUI_H_
#define IUI_H_

#include <iostream>

/* Interface
* pour l'UI
* (oblige a implementer refresh();
*/
class IUI
{
public:
IUI();
~IUI();
virtual void refresh(){ std::cout << "Mauvais refresh" << std::endl; }
};

#endif /*IUI_H_*/
/***********************************************************/




/* IUI.cpp */
/***********************************************************/
#include "IUI.h"

IUI::IUI()
{
}


IUI::~IUI()
{
}
/***********************************************************/




/* ChessFrame.h */
/***********************************************************/
#ifndef CHESSFRAME_H_
#define CHESSFRAME_H_

#include "wx/wx.h"
#include "ChessCanvas.h"
#include "net.h"
#include "IUI.h"
#include <iostream>

class ChessFrame : public wxFrame, public IUI {
public:
ChessFrame();
virtual ~ChessFrame();
virtual void refresh() { std::cout << "Bon refresh" << std::endl; }
...
private:
...
};

#endif /*CHESSFRAME_H_*/
/***********************************************************/



/* Controlleur.cpp */
/***********************************************************/
cout << "Call... ";
/* this->_UI est declaree de type IUI,
* mais correspond a une instance de ChessFrame
*/
this->_UI->refresh();
cout << "Called..." << endl;
/***********************************************************/



Voila donc ce qui m'est renvoyé sur la console:

Call... Called...
Call... Called...
Call... Called...
Call... Called...

Normalement, je devrais avoir la trace de la méthode appelée entre Call
et Called, mais ce n'est pas le cas.

Auriez vous une idée sur la source du problème ?

Merci en tout cas aux personnes qui auront lu ce thread jusqu'à la fin,
et si quelqu'un veut plus de précisions, j'y répondrai :)

6 réponses

1 2
Avatar
kanze
Sylvain wrote:
Arnaud Meurgues wrote on 09/03/2006 13:03:
BOUCNIAUX Benjamin wrote:

Bah je me fais mes casts a l'ancienne, à la C (IUI *),
(ChessFrame *)... Cela poste t'il un probleme en C++ ?


Cele revient à faire un reinterpret_cast qui ne permet pas
de discerner l'intention de l'auteur (et qui a un
comportement indédini).


?? écrire IUI* ui = (IUI*) truc; ne permet pas de "distinguer
l'intention" de caster le pointer 'truc' en IUI*; là aussi
c'est "moins lisible" (c)(r)(tm) ?


Qu'est-ce que tu essaies à dire là ? Les casts à la C ont
plusieurs significations, selon le cas, et ce n'est pas toujours
évident laquelle s'applique. Comme j'ai dit par ailleurs, j'ai
même eu le cas où une modification ailleurs en a changé la
signification.

la surcharge faite ainsi à un comportement *très* défini,


Le comportement est bien défini, mais il dépend de la contexte
d'une façon pas du tout évident. Et ici, utiliser le résultat
d'un reinterpret_cast a bien un comportement indéfini, même si
le cast ne l'a pas.

il mappe le ptr sur la TMV de la classe IUI; c'est le premier
appel où un déréférencement quelconque du pointeur qui peut
avoir un comportement indéfini ...


C'est quoi la TMV ?

Utiliser les casts C++ permet de rendre explicite ce que
l'on cherche à faire.


ils doivent être privilégiés (au dela de l'"intention") dans
tous les cas (sauf où c'est impossible, passage par void* par
exemple).


Comment, c'est impossible lors d'un passage par void* ? Je m'en
sert tout le temps quand j'ai à faire avec les interfaces Posix,
par exemple.

mais il est encore plus propre de s'en passer complètement;
l'upcast traduit souvent une faiblesse (une mauvaise
définition) de la classe de base; lui ajouter la virtuelle
pure adéquate évitera un upcast.


Seulement que dans ce cas-ci, il n'a pas de contrôle sur les
classes de base.

C'est un cas fréquent dans les GUI, ou d'autres systèmes qui
génèrent des évenemments. C'est que la GUI, lui, ne connaît pas
ton application, et est donc obligée à maintenir des pointeurs à
des types qu'il connaît.

dans le cas d'un héritage multiple, l'héritage virtuel peut
être employé si indispensable;


Mais ce n'est évidemment pas le cas ici.

l'erreur plus classique est encore lié au modèle, un bout de
code recevant une référence parent "d'une des branches
d'héritage" ne devrait pas avoir à invoquer des méthodes de
l'autre branche (ou alors il vaut mieux lui transmettre une
référence de la classe noeud de l'héritage).


C'est un beau principe. Dans le pratique, quand on travaille
avec deux logiciels tiers, on n'a pas toujours le choix.

--
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
Sylvain
Arnaud Meurgues wrote on 10/03/2006 09:10:

Ben non. Il peut très bien, sur du code non trivial, penser faire un un
static cast de style down cast qui devient en fait un reinterpret_cast
parce que les types ne peuvent se caster.


oui ?! t'as pas un exemple ? parce que "stroumpfer le stroumpf que l'on
peut pas stroumpfer sauf s'il se stroumpfe tout seul" je saisis pas bien.

Lorsque je lis

IUI* ui = (IUI*)truc;

Je ne sais absolument pas si le truc en question est supposé pouvoir se
caster ou si c'est pour faire un mapping sale à la C.


si tu regardes sa définition postée, tu ne trouves aucun opérateur de
surcharge, de plus "truc" est un pointeur et AFAIK un opérateur de cast
s'applique à une référence, donc, oui, « c'est du sale C » qui marchera
très bien en héritage simple et peut partir dans le décor pour un
héritage multiple.

Mais si le pointeur n'est jamais déréférencé, je ne suis pas
certain que le cast ait le moindre sens. [...]


vraiment ?

Sylvain.

Avatar
Arnaud Meurgues
Sylvain wrote:

Ben non. Il peut très bien, sur du code non trivial, penser faire un un
static cast de style down cast qui devient en fait un reinterpret_cast
parce que les types ne peuvent se caster.
oui ?! t'as pas un exemple ? parce que "stroumpfer le stroumpf que l'on

peut pas stroumpfer sauf s'il se stroumpfe tout seul" je saisis pas bien.


Voir le message de James.

Mais si le pointeur n'est jamais déréférencé, je ne suis pas
certain que le cast ait le moindre sens. [...]
vraiment ?



Là, j'ai un peu de mal à trouver un cas où le cast sert à quelque chose
sans déréférencement ultérieur. Vous avez un exemple ?

--
Arnaud


Avatar
Gabriel Dos Reis
Sylvain writes:

| Arnaud Meurgues wrote on 10/03/2006 09:10:
| > Ben non. Il peut très bien, sur du code non trivial, penser faire un
| > un
| > static cast de style down cast qui devient en fait un reinterpret_cast
| > parce que les types ne peuvent se caster.
|
| oui ?! t'as pas un exemple ? parce que "stroumpfer le stroumpf que
| l'on peut pas stroumpfer sauf s'il se stroumpfe tout seul" je saisis
| pas bien.

moi non plus, mais qui parle de stroumpf?

-- Gaby
Avatar
Sylvain
Arnaud Meurgues wrote on 10/03/2006 22:55:
Sylvain wrote:

Ben non. Il peut très bien, sur du code non trivial, penser faire un un
static cast de style down cast qui devient en fait un reinterpret_cast
parce que les types ne peuvent se caster.
oui ?! t'as pas un exemple ? parce que "stroumpfer le stroumpf que l'on

peut pas stroumpfer sauf s'il se stroumpfe tout seul" je saisis pas bien.


Voir le message de James.


ah bon, comprendre Arnaud dans le texte (1) présuppose lire James qui
postera 1h06 plus tard, c'est limpide; au delà le point (plus illustré)
de James illustre mieux, en effet, les "bonnes écritures".

(1):
Il peut très bien, sur du code non trivial,
le code est ici présent dans le premier post et *est* trivial



penser faire un
donc on conjecture sur ce qu'il aura peut être pensé faire


("on" fait du C++ ou de la voyance ?)

un static cast de style down cast
donc un truc qui ne sert à rien


(pour rappel, Cf Bjarne St., The C++ Prog. Lang. § 6.2.7)

qui devient en fait un reinterpret_cast
non qui aurait du être codé *explicitement* "reinterpret" (ou mieux


"dynamic"), le compilo ne risque pas d'insérer ce statement tout seul

parce que les types ne peuvent se caster.
cela n'empêche pas le cast "à la C" à forcer le compilateur à considérer


l'adresse argument comme un pointeur valide et donc autoriser l'accès à
des données ou fonctions membres de sa classe (avec les risques déjà
décrits).

Mais si le pointeur n'est jamais déréférencé, je ne suis pas
certain que le cast ait le moindre sens. [...]
vraiment ?



Là, j'ai un peu de mal à trouver un cas où le cast sert à quelque chose
sans déréférencement ultérieur. Vous avez un exemple ?


excuse-moi, c'était de l'ironie
a) rappeller que "ce qui ne sert à rien", "ne sert à rien" ne m'avait
pas paru si indispensable,
b) je n'ai pas dit qu'il fallait caster un pointeur pour ne
plus-jamais-surtout le déréférencer, mais au contraire que la bombe se
déclenchera sur *ce* déréférencement (qui existe donc).

Sylvain.



Avatar
Sylvain
Arnaud Meurgues wrote on 10/03/2006 22:55:

Mais si le pointeur n'est jamais déréférencé, je ne suis pas
certain que le cast ait le moindre sens. [...]
vraiment ?



Là, j'ai un peu de mal à trouver un cas où le cast sert à quelque chose
sans déréférencement ultérieur. Vous avez un exemple ?

désolé c'était de l'ironie; je n'ai pas dit qu'il fallait caster un

pointeur pour ne pas le déréférencer, mais au contraire que la bombe se
déclenchera sur ce déréférencement (qui existe donc).


Sylvain.



1 2