OVH Cloud OVH Cloud

Problème classes abstraites ...

12 réponses
Avatar
Hamiral
Bonjour,

J'ai un ensemble de classes comme suit :

class Renderable {
friend class Renderer;

public:
Renderable() {};
virtual ~Renderable() {};
virtual Renderer* getRenderer() = 0;
}

class Renderer{
public:
Renderer() {};
virtual ~Renderer() {};
virtual void render(const Renderable* const renderable) = 0;
};

class Mesh : Renderable {
friend class MeshRenderer;

public:
Mesh() { ... };
~Mesh() { ... };
virtual MeshRenderer* getRenderer() { return new MeshRenderer; };
};

class MeshRenderer : public Renderer
{
public:
MeshRenderer() { ... };
virtual ~MeshRenderer() { ... };
virtual void render(Mesh* const mesh) { ... };
};

Et quand je compile cela, je me retrouve systématiquement avec les messages
d'erreur suivants :

/.../mesh.cpp: In member function 'virtual MeshRenderer*
Mesh::getRenderer()':
/.../mesh.cpp:28: error: cannot allocate an object of abstract
type 'MeshRenderer'
/.../meshrenderer.h:14: note: because the following virtual functions are
pure within 'MeshRenderer':
/.../renderer.h:17: note: virtual void Renderer::render(const Renderable*)

Ce que je ne comprends pas, c'est que la méthode Renderer::render(const
Renderable*) n'est PAS pure !!! Je ne comprends pas pourquoi le compilateur
la traite comme telle ?
Si, dans Renderer, je change la méthode virtuelle pure render() en non pure,
cela compile, mais ça ne correspond alors plus à mon design ... Seulement
je ne trouves pas où se trouve mon erreur ; quelqu'un peut m'aider ?

Merci infiniment par avance !
--
Hamiral

2 réponses

1 2
Avatar
James Kanze
Loïc Joly wrote:

Seulement j'ai maintenant ce message d'erreur :
/.../meshrenderer.cpp:19: error: invalid conversion from 'Renderable*'
to 'Mesh*'



Or, ma classe Mesh dérive de Renderable ... Je présume donc qu'il me
manque
un xxx_cast<...> quelque part, non ?



Oui, un
Mesh* mesh = dynamic_cast<Mesh*>(renderable);
if (mesh != NULL)
{ // ...
}


Mais souvent, c'est signe d'un design faible de devoir faire
ça, puisque ça couple les classes de rendu avec les classes
concrètes que l'on peut rendre.


Souvent, oui. Mais ici, je ne crois pas.

Es-tu certain qu'il n'y a pas moyen de travailler dans cette
fonction avec un Renderable*, sans savoir son type précis ?


A priori, vue les noms, je le doute. Ou plutôt, la remède est
peut-être pire que la maladie. En gros, si j'ai bien compris,
ses objets sont des agents. Il a un ensemble de Renderable ; il
récupère un ensemble de Renderer, pour utilisation plus tard.
Quand il les utilise, il appelle la fonction du Renderer chaque
fois avec le Renderable qui l'a créé.

D'un certain côté, on peut le considérer comme une variation du
modèle visiteur ; en tout cas, il vise le même problème. Sauf
qu'en ajoutant encore la classe intermédiaire, on élimine la
nécessité que le visiteur connaisse toutes les classes ciblées.
Chaque classe ciblée définisse son propre visiteur.

Évidemment, la plupart du temps, c'est beaucoup plus simple
d'ajouter une fonction virtuelle dans la Base. Mais on peut bien
imaginer les cas où on ne veut pas surcharger l'hièrarchie de
cette fonctionnalité.

--
James Kanze
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
tristan.carel
Hamiral wrote:
wrote:
Le code ci-dessus est agence de facon a compiler dans un seul fichier.
Mais il n'y a aucun probleme pour separer l'objet fonctoin `Renderer'
des `Renderable' modulo quelques forward declarations ;)


Merci beaucoup, je pense que ça va m'aider à me débloquer dans mon
développement :)


Oui certes, mais cet exemple est assez limitatif. La faille se trouve
dans le fait que la methode `accept' des classes appartenant a la
hierarchie des `Renderable' n'accepte que des `Renderer'. Cela
signifie en d'autres termes que dans le cas ou tu souhaites implementer
une nouvelle operation `Foo' s'appliquant sur des `Renderable', dans
l'etat actuel des choses tu es oblige de redefinir une nouvelle methode
`accept' qui prennent du `Foo' en parametre.
Ceci n'est pas du tout pratique car aussi crade que lourd a maintenir.

Ici `Foo' et `Renderer' ont pourtant une chose en commun: ils
effectuent tous les deux des operations a partir de `Renderable'.
Une solution serait alors de modifier la signature de la methode
`accept' de tes `Renderable' comme suit:

void Renderable :: accept(Visitor& e);

Visitor serait une classe abstraite contenant les meme methodes que
celle de `Renderer', modulo des virtual = 0 partout. `Ensuite il te
"suffit" de faire deriver `Renderer' et `Foo' de `Visitor' et c'est
gagne.


Cette solution est tout a fait acceptable mais bien loin d'etre
parfaite. J'ai l'habitude d'enlever les const lorsque c'est necessaire
plutot que les mettre.
Dans l'exemple poste precedemment la signature de `accept' de
`Renderable' etait:

virtual void accept(Renderer&) const

`const' car j'ai suppose qu'une methode effectuant un rendu n'est pas
censee modifier les donnees sur lesquelles elle travaille.

Mais imaginons que `Foo' soit une operation qui elle modifie les
donnees de `Renderable', on est alors oblige de perdre la constitude
sur les methodes `accept' des `Renderable' et les `operator ()' de
`Visitor' et `Renderer'.

donc on a un dileme:
`Foo' veut avoir : void Renderable :: accept(Visitor& e);
`Renderer' se suffit avec : void Renderable :: accept(Visitor& e)
const;

On pourrait tres bien oublier tous les const et roulezzz mais on perd
le fait qu'il existe des operations qui n'ont pas besoin de modifier
les objets sur lesquelles elles bossent et que d'autres sont la pour
justement les modifier. C'est egalement dangereux car tu pourrais
modifier tes objets par erreurs dans tes methodes `Renderer' et le
compilo ne te dirait rien.

Une solution serait donc de creer deux hierarchies de visiteurs
s'appliquant sur les `Renderable':

class ConstVisitor
{
public:
virtual void operator () (const Renderable&) = 0;
// ...
};

class Visitor
{
public:
virtual void operator () (Renderable&) = 0;
};


`Renderer' et `Foo' heriteraient respectivement de `ConstVisitor' et
`Visitor'.



Ta hierarchie de `Renderable' seraient alors modifiee comme suit:
class Renderable
{
public:
virtual void operator () (ConstVisitor&) const = 0;
virtual void operator () (Visitor&) = 0;
};

Pour accepter a la fois des operations pouvant modifier les donnees,
d'autres qui ne font que les utiliser.

En esperant avoir ete clair...
CU
--
Tristan Carel
http://wcube.epita.fr/~tristan


1 2