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

10 réponses

1 2
Avatar
Fabien LE LEZ
On Mon, 06 Mar 2006 15:34:54 +0000, BOUCNIAUX Benjamin
:

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.


Appelle select() de temps en temps (avec un timeout de 0), et
n'appelle recv() que si select() t'indique qu'il y a des données en
attente. Ainsi recv() ne sera jamais bloquant.

virtual void refresh(){ std::cout << "Mauvais refresh" << std::endl; }


Pourquoi cette fonction n'est-elle pas virtuelle pure ?

Qu'est-ce que ça donne si tu renommes ta fonction ? "Refresh" est un
mot très courant, il est fort possible qu'une fonction de même nom
soit utilisée ailleurs.

Avatar
Sylvain
BOUCNIAUX Benjamin wrote on 06/03/2006 16:34:

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.


la gestion de l'UI est généralement facilité dans le thread principal,
mais pourquoi ne pas faire l'acquisition (souvent bloquante) dans un
thread secondaire ?

#include <iostream>
class IUI {
public:
IUI();
~IUI();
virtual void refresh(){ std::cout << "Mauvais refresh" << std::endl; }
};


ou:
class IUI {
public:
IUI() {}
virtual ~IUI() {}
virtual void refresh() = null;
};

a) un destructeur d'une classe devant être dérivée doit être virtuelle
afin qu'une instance dynamique (allouée) d'une classe fille soit
correctement détruite.

b) la méthode refresh est avantageusement virtuelle pure.

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


(Rmq: ici les 2 "virtual" sont optionnels)

/* Controlleur.cpp */
/***********************************************************/
cout << "Call... ";
this->_UI->refresh();
cout << "Called..." << endl;
/***********************************************************/


tu n'indiques pas si le code contient un "using std", on ne peut
affirmer à cette seule lecture que "std::cout" et "cout" sont le même flux.

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


et le débuggeur pas-à-pas, il jumpe où ?

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


2 streams différents ou, sans y croire, un étrange effet de bord sur un
stream non flushé.

Sylvain.

Avatar
BOUCNIAUX Benjamin
Bonjour.

Bon alors en fait, je sens que je vais me faire lancer des pierres dessus
:p

Le probleme etait simplement que en gros:

J'avais une classe ChessFrame sur laquelle je devais appeler la methode
refresh().

Seulement, je passais le pointeur sur cette classe, en le castant en
wxFrame à un autre endroit dans le code.
Puis quand je devais appeler la méthode, je le faisais en la recastant en
IUI. Bien évidemment, ca marchait pas ...

Il a donc suffit de passer le type du pointeur de 'wxFrame' à 'IUI', et
tout a refonctionné :/

Des fois on réfléchit trop et ne voit pas tout ;)

Sinon, merci pour les réponses.

Pour le thread, j'y avais pensé, mais c'était en solution de secours à
vrai dire. J'avais pas envie de m'****erder avec des shm / mutex & co
Bon la ca marche en faisant des appels non bloquants à recv() et en
rafraichissant régulièrement avec un petit timeout pour ne pas bouffer
toutes les ressources de la machine.

Pour la deuxieme solution, avec select, il est vrai que ça aurait pu
résoudre mon problème d'appels bloquant également, et je ne l'avais pas
envisagé. Cela dit, il aurait été plus propre d'utiliser cet appel
plutot qu'un bete while(recv(...) < 0) { refresh(); usleep() }

M'enfin ... :p

Bon sinon, un autre problème se pose (ouais je suis chiant):
Au bout d'un certain nombre d'appels à la méthode refresh(), qui en soit
effectue juste 2 appels aux méthodes Refresh() et Update() de la classe
wxFrame (en fait les méthodes sont héritées de wxWindow), je me choppe
une bel erreur BadAlloc de la part du serveur X11.

Savez vous si ces méthodes font des appels qui allouent et libèreraient
mal la mémoire ? (dans quel cas je serais bien dans la merde).


En tout cas, merci et bonne journée.
Avatar
kanze
BOUCNIAUX Benjamin wrote:

Bon alors en fait, je sens que je vais me faire lancer des
pierres dessus
:p

Le probleme etait simplement que en gros:

J'avais une classe ChessFrame sur laquelle je devais appeler
la methode refresh().

Seulement, je passais le pointeur sur cette classe, en le
castant en wxFrame à un autre endroit dans le code. Puis
quand je devais appeler la méthode, je le faisais en la
recastant en IUI. Bien évidemment, ca marchait pas ...


Je ne comprends pas. Si tu utilises dynamic_cast, ça doit
marcher. Si tu utilises static_cast, tu dois avoir une erreur à
la compilation. Et tu n'aurais certainement pas utilisé
reinterpret_cast, et const_cast ne permet pas de passer d'un
wxFrame* à un IUI*.

Alors, qu'est-ce que tu as fait comme cast ?

Il a donc suffit de passer le type du pointeur de 'wxFrame' à
'IUI', et tout a refonctionné :/

Des fois on réfléchit trop et ne voit pas tout ;)

Sinon, merci pour les réponses.

Pour le thread, j'y avais pensé, mais c'était en solution de
secours à vrai dire. J'avais pas envie de m'****erder avec des
shm / mutex & co Bon la ca marche en faisant des appels non
bloquants à recv() et en rafraichissant régulièrement avec un
petit timeout pour ne pas bouffer toutes les ressources de la
machine.


Dans la mesure où on n'en a pas besoin, mieux vaut éviter les
threads, c'est sûr.

Pour la deuxieme solution, avec select, il est vrai que ça
aurait pu résoudre mon problème d'appels bloquant également,
et je ne l'avais pas envisagé. Cela dit, il aurait été plus
propre d'utiliser cet appel plutot qu'un bete while(recv(...)
< 0) { refresh(); usleep() }

M'enfin ... :p


Logiquement, si la bibliothèque est conçue pour une utilisation
dans une contexte mono-thread, il doit être possible de définir
des évenemments supplémentaires, qui se déclenche par exemple
quand il y a quelque chose de disponible sur un socket.

Bon sinon, un autre problème se pose (ouais je suis chiant):
Au bout d'un certain nombre d'appels à la méthode refresh(),
qui en soit effectue juste 2 appels aux méthodes Refresh() et
Update() de la classe wxFrame (en fait les méthodes sont
héritées de wxWindow), je me choppe une bel erreur BadAlloc de
la part du serveur X11.


Tu ne libères pas la mémoire dynamique que tu dois libérer ?

Une autre possibilité, c'est qu'il arrive des évenemments que tu
ne traites pas. J'imagine que les évenemments non encore traités
sont stockés dans de la mémoire dynamique.

Il y a enfin le problème classique : suite à une erreur avec un
pointeur ailleurs, l'espace de mémoire dynamique est corrompu,
et malloc n'arrive plus à rétrouver les siens.

Malheureusement, la plus souvent, c'est ce dernier, et c'est de
loin la plus difficile à trouver.

Savez vous si ces méthodes font des appels qui allouent et
libèreraient mal la mémoire ? (dans quel cas je serais bien
dans la merde).


Je doute fort qu'il y a une fuite de mémoire dans wxWidgets
quand on s'en sert correctement, mais c'est toujours 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

Avatar
BOUCNIAUX Benjamin
Le Thu, 09 Mar 2006 00:54:15 -0800, kanze a écrit :

BOUCNIAUX Benjamin wrote:

Je ne comprends pas. Si tu utilises dynamic_cast, ça doit
marcher. Si tu utilises static_cast, tu dois avoir une erreur à
la compilation. Et tu n'aurais certainement pas utilisé
reinterpret_cast, et const_cast ne permet pas de passer d'un
wxFrame* à un IUI*.

Alors, qu'est-ce que tu as fait comme cast ?



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

Tu ne libères pas la mémoire dynamique que tu dois libérer ?

Une autre possibilité, c'est qu'il arrive des évenemments que tu
ne traites pas. J'imagine que les évenemments non encore traités
sont stockés dans de la mémoire dynamique.

Il y a enfin le problème classique : suite à une erreur avec un
pointeur ailleurs, l'espace de mémoire dynamique est corrompu,
et malloc n'arrive plus à rétrouver les siens.

Malheureusement, la plus souvent, c'est ce dernier, et c'est de
loin la plus difficile à trouver.



Ouep en fait, il serait possible qu'il y aie un memory leak tout con ...
Je viens de me rendre compte qu'a chaque rafraichissement un new wxBitmap()
est effectué sans desallouer l'ancien emplacement.
Le programme tourne, j'attend de voir ce que ça donne après correction.
A priori, avec un free qui tourne a coté, je ne vois plus les allocations
mémoire grimper en pointe.

Je doute fort qu'il y a une fuite de mémoire dans wxWidgets
quand on s'en sert correctement, mais c'est toujours possible.


C'est ce que je me dis également. Mais dans le doute, je tenais quand
même à demander. Et sur les ML des developpeurs de wx, ils m'ont dit la
même chose :)

Merci.

Avatar
Arnaud Meurgues
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).

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

--
Arnaud

Avatar
Sylvain
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) ?

la surcharge faite ainsi à un comportement *très* défini, 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 ...

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

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.

dans le cas d'un héritage multiple, l'héritage virtuel peut être employé
si indispensable; 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).

Sylvain.


Avatar
Arnaud Meurgues
Sylvain wrote:

?? é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) ?


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.

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.

la surcharge faite ainsi à un comportement *très* défini, 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 ...


Certes. Mais si le pointeur n'est jamais déréférencé, je ne suis pas
certain que le cast ait le moindre sens. À quoi pourrait servir un cast
si le pointeur n'est ensuite jamais déréférencé ?

--
Arnaud

Avatar
kanze
BOUCNIAUX Benjamin wrote:

BOUCNIAUX Benjamin wrote:

Je ne comprends pas. Si tu utilises dynamic_cast, ça doit
marcher. Si tu utilises static_cast, tu dois avoir une
erreur à la compilation. Et tu n'aurais certainement pas
utilisé reinterpret_cast, et const_cast ne permet pas de
passer d'un wxFrame* à un IUI*.

Alors, qu'est-ce que tu as fait comme cast ?


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


Ça pose le problème que tu as fait un cast, sans savoir de quel
type. En C, c'est simple : les casts de pointeur sont des
reinterpret_cast, et les autres des static_cast. En C++, quand
on se trouve à l'intérieur d'une hiérarchie d'héritage, on a et
reinterpret_cast, et static_cast sur des pointeurs -- un cast à
la C, c'est un static_cast, quand un static_cast est légal, et
un reinterpret_cast, quand ce n'est pas le cas.

Pour naviguer dans un hiérarchie, il faut dynamic_cast, ou, si
on est 100% sur de ce qu'on fait, et qu'on a des problèmes de
performance, static_cast. reinterpret_cast, c'est pour le « type
punning » -- tu prends l'adresse d'un wxFrame, et tu dis au
compilateur que c'est en fait l'adresse d'un IUI*. Et le type
punning, avec des types élaborés, ce n'est jamais ce que tu
veux.

Avec ton hiérarchie, si tu avais fait static_cast< IUI* >(
wxWPtr ), tu aurais eu une erreur de compilation. Tu aurais donc
su que ça ne marche pas.

--
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
kanze
Arnaud Meurgues wrote:
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).


Celà revient surtout à faire ou un reinterpret_cast ou un
static_cast, selon où on se trouve dans l'hiérarchie. Dans son
cas :

IUI* pIUI ;
wxFrame* pWxW ;
ChessFrame* pChess ;

(IUI*)pChess ==> static_cast
(wxFrame*)pChess ==> static_cast
(ChessFrame*)pIUI ==> static_cast
(ChessFrame*)pWxW ==> static_cast
(IUI*)pWxW ==> reinterpret_cast
(wxFrame*)pIUI ==> reinterpret_cast

J'ai déjà eu le cas (il y a longtemps, avant les nouveaux casts)
où une évolution dans la hiérarchie a changé un static_cast en
reinterpret_cast.

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


Et parce que c'est explicite, le compilateur peut détecter les
erreurs quand l'opération n'est pas supportée -- le problème
avec les casts à la C, c'est que quand l'opération que tu veux
n'est pas supportée, le compilateur en utilise un autre.

Aussi, je crois que certains compilateurs génèrent un
avertissement dans le cas d'un cast à la C. Encore que là, je
préfèrerait qu'ils ne le fassent que pour les casts entre
pointeurs ou entre références -- je préfère même (double)i à
static_cast< double >( i ).

(Ceci dit : dans la contexte où il se trouve, le seul cast qui
convient, AMHA, c'est dynamic_cast.)

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