OVH Cloud OVH Cloud

polymorphisme de fonction membre

7 réponses
Avatar
Stanislas RENAN
bonjour,

j'essaye de faire quelque chose qui ne fonctionne pas :
j'aimerais avoir une classe de base qui permette de définir une
opération "appel de fonction" sur une instance, la fonction étant
identifiée par une chaîne.
L'implantation de l'appel de fonction est fait dans des classes dérivées
de la classe de base qui met en place le mécanisme d'association
chaîne <-> fonction.
L'instance citée plus haut est d'un type paramètré : je veux pouvoir
mettre en oeuvre le mécanisme sur des objets divers et variés.

J'ai commencé à implanter ça via des pointeurs sur fonction membre,
mais c'était oublier que le type connu dans la classe de base est
Base::*() et non pas Derivee::*(), et que si je peux écrire :
Derivee * d;
Base * b = d;

Je ne peux pas écrire :
void (Derivee::*g)();
void (Base::*f)() = g;

qui donne l'erreur :
error C2440: 'initializing' : cannot convert from 'void (__thiscall
Derivee::*)(void)' to 'void (__thiscall Base::*)(void)'
Types pointed to are unrelated; conversion requires
reinterpret_cast, C-style cast or function-style cast

Le code ci-dessous donne une autre version de cette même erreur :
error C2664: 'associe' : cannot convert parameter 2 from 'class Variable
*(__thiscall RepresentantObjet::*)(void) const' to 'class Variable
*(__thiscall Representant<class Objet>::*)(void) const'
Types pointed to are unrelated; conversion requires
reinterpret_cast, C-style cast or function-style cast


voilà mon code actuel :
===

#include <map>
#include <string>

class Variable {};

class Objet
{
public:
Variable * a() const { return new Variable; }
Variable * b() const { return new Variable; }
Variable * c() const { return new Variable; }
};

template <typename T>
class Representant
{
protected:
typedef Variable* (Representant::*Func)() const;
typedef std::map<std::string, Func> Fonctions;
T & m_instance;

private:
static Fonctions m_map;

protected:
Representant(T& inst) : m_instance(inst) {}
virtual void doInitialize()=0;

protected:
void associe(const std::string& s, Func f) { m_map[s] = f; }
Func recupere(const std::string& s) const
{
Fonctions::const_iterator it = m_map.find(s);
if (it != m_map.end())
return *it;
else
return 0;
}

public:
static init(Representant<T>& t) { t.doInitialize(); }
Variable * call(std::string& name) { Func f = recupere(name); return
(f == 0)?0:m_instance.(*f); }
};

class RepresentantObjet : Representant<Objet>
{
private: // accès depuis la classe de base via un pointeur...à voir
Variable * doA() const { return m_instance.a(); }
Variable * doB() const { return m_instance.b(); }
Variable * doC() const { return m_instance.c(); }
protected:
/* virtual */ void doInitialize()
{
associe("A", &RepresentantObjet::doA);
associe("B", &RepresentantObjet::doB);
associe("C", &RepresentantObjet::doC);
}
public:
RepresentantObjet(Objet& instance) : Representant<Objet>(instance) {}
};

===

j'ai cherché un pattern qui correspondrait, plutôt que de réinventer
la roue, car le problème me semble assez simple et probablement courant,
mais je n'ai pas trouvé pour l'instant.

Ce que je souhaite pouvoir écrire :

Objet o;
Representant<T>* r = new RepresentantObjet(o);
RepresentantObjet::initialize(*r); // appelé une unique fois en début de
// programme.
Variable * var = r->call("A"); // la substantifique moëlle : on
// appelle "A" sur l'objet dont on a le représentant pointé par r,
// peu importe ce qu'il représente réellement.

je souhaite bien évidemment ne pas devoir réécrire le mécanisme pour
chaque type de représentant.

une idée ? des idées ? plein d'idées ?
--
Stanislas RENAN

7 réponses

Avatar
Stanislas RENAN
une idée ? des idées ? plein d'idées ?


oui, je me réponds à moi-même, ça fait toujours classe...

héritage + fonction = functor.
Ce problème réglé, un autre arrive avec ses sabots :

écrire :
Representant<T>* r = new RepresentantObjet(o);
n'est pas possible.

Pfff.
--
Stanislas RENAN

Avatar
Christophe Lephay
"Stanislas RENAN" a écrit dans le message de news:
41deb138$0$5715$
écrire :
Representant<T>* r = new RepresentantObjet(o);
n'est pas possible.


Il faut que tes templates héritent tous d'une classe de base :

class RepresentantObject;

template< class T >
Representant : public RepresentantObject
{
...
};

Avatar
Stanislas RENAN

Il faut que tes templates héritent tous d'une classe de base :


oui, c'est ce que je viens de faire, en me rendant compte que
instanciation de classes implique que les classes instanciées n'ont
aucun lien entre elles, sinon l'écriture du code source.

class RepresentantIf
{
public:
virtual Variable * call(std::string& name)=0;
virtual void doInitialize()=0;

static init(RepresentantIf& t) { t.doInitialize(); }
};

// Représentant de base
template <typename T>
class Representant : public RepresentantIf
{
// ...
};

Maintenant, j'ai une spécialisation de fonction virtuelle de mon fonctor
qui n'est pas prise en compte, et je bloque dessus.
Visiblement, mon code est de nouveau incorrect :

// Functor de base
template <typename T>
class RepresentantFunctor
{
protected:
std::string m_name;
public:
RepresentantFunctor(std::string name) : m_name(name) {}
virtual Variable * operator()(const T& t) const { throw; }
bool operator<(const RepresentantFunctor<T>& t) const { return m_name
< t.m_name; }
};

// Functor dérivé A
class RepresentantFunctorA : public RepresentantFunctor<Objet>
{
public:
RepresentantFunctorA() : RepresentantFunctor<Objet>("A") {}
Variable * operator()(const Objet& t) const { return t.a(); }
};

un appel à operator()(const Objet&) sur une instance de
RepresentantFunctorA via un pointeur sur la classe template de base
RepresentantFunctor<Objet> appelle :

RepresentantFunctor<Objet>::operator()(const Objet&)

et non pas comme je l'aurais souhaité :

RepresentantFunctorA::operator()(const Objet&)

C'est ce qui m'agace un peu dans C++ : c'est très puissant, mais on est
obligé d'écrire parfois des choses alambiquées pour implanter un concept
a priori simple.
Enfin, alambiquées pour moi, qui n'a pas :)
Et en plus, là, ça ne fonctionne pas.

Je sens que je vais oublier la paramètrisation template, et faire une
petite hiérarchie toute simple, plus lisible, et qui fonctionne :(
--
Stanislas RENAN

Avatar
Loïc Joly
Stanislas RENAN wrote:

Visiblement, mon code est de nouveau incorrect :

// Functor de base
template <typename T>
class RepresentantFunctor
{
protected:
std::string m_name;
public:
RepresentantFunctor(std::string name) : m_name(name) {}
virtual Variable * operator()(const T& t) const { throw; }
bool operator<(const RepresentantFunctor<T>& t) const { return m_name
< t.m_name; }
};

// Functor dérivé A
class RepresentantFunctorA : public RepresentantFunctor<Objet>
{
public:
RepresentantFunctorA() : RepresentantFunctor<Objet>("A") {}
Variable * operator()(const Objet& t) const { return t.a(); }
};

un appel à operator()(const Objet&) sur une instance de
RepresentantFunctorA via un pointeur sur la classe template de base
RepresentantFunctor<Objet> appelle :

RepresentantFunctor<Objet>::operator()(const Objet&)

et non pas comme je l'aurais souhaité :

RepresentantFunctorA::operator()(const Objet&)




J'ai quelques petites questions à te poser :
- Déjà, je n'ai pas compris ce que tu veux en fait...
- De ce que j'ai compris, je me demande si boost::bind n'a pas un effet
semblable
- Pourquoi RepresentantFunctorA n'est-il pas templaté ?
- Je n'arrive pas à reproduire ton problème. Voici mon code :
#include <iostream>
using namespace std;

class Object{};

template <class T> struct A
{
virtual int operator()(T const &) {return 1;}
};

struct B:A<Object>
{
virtual int operator()(Object const &) {return 2;}
};

int main()
{
A<Object> *a = new B;
Object o;
cout << a->operator()(o) << endl;
cin.ignore();
}

Avatar
Repondre sur le NG

J'ai quelques petites questions à te poser :
- Déjà, je n'ai pas compris ce que tu veux en fait...
j'ai des objets quelconques qui représentent des données.

Je veux pouvoir permettre à un utilisateur d'accéder à ces donnée s via
un mécanisme générique et nommé.

Par exemple si la classe A possède 2 fonctions f et g renvoyant l'une u n
double et l'autre une chaîne, il doit pouvoir accéder à ces
informations.
On doit lui présenter "f" et "g".

Ensuite, une fois qu'il a sélectionné ou bien "f" ou bien "g", il fau t
être en mesure d'effectuer l'opération f() ou g(), et d'obtenir la
valeur.

Les valeurs sont gérées par une classe Variable non développée ic i.
Je cherche à implanter ce que j'ai appelé un représentant de A, et
notament de A::f() et de A::g().

Je veux pouvoir représenter n'importe quoi, et obtenir un mécanisme
unique qui permette de traiter la valeur de retour, quel que soit ce qui
est représenté et le type de la valeur de retour.
Je veux que ce mécanisme soit simple à étendre, tant dans les class es à
représenter que dans les fonctions des classes représentées.

Le problème est simple dans la mesure où tout est connu à la
compilation, et qu'on ne représente que des données, donc cela revien t à
une fonction sans argument.

- De ce que j'ai compris, je me demande si boost::bind n'a pas un effet
semblable
Je n'ai pas eu le temps de regarder toutes les bibliothèques de boost,

tellement c'est riche et complexe. En jetant un oeil sur boost::bind, je
n'ai pas vraiment l'impression que ce soit ce que je veux faire.

- Pourquoi RepresentantFunctorA n'est-il pas templaté ?
pourquoi devrait-il l'être ?

Chaque RepresentantFunctorQQCHOSE est différent des autres, et n'est
pas destiné à être paramètré dans la mesure où il est le lien final
entre le représentant et la donnée représentée.

- Je n'arrive pas à reproduire ton problème. Voici mon code :
Je ne peux pas accéder à mon code ce WE.

Je voulais confirmer le comportement en le compilant avec comeau en
ligne, mais je n'ai pas eu le temps. Je le ferai lundi.

Si effectivement cela fonctionne, alors mon compilateur est buggé.
Ça ne serait pas impossible (c'est VC++ 6.0), mais ce qui est le plus
souvent source d'erreur, c'est moi-même :)
--
Stanislas RENAN

Avatar
Loïc Joly
Repondre sur le NG wrote:


J'ai quelques petites questions à te poser :
- Déjà, je n'ai pas compris ce que tu veux en fait...


j'ai des objets quelconques qui représentent des données.
Je veux pouvoir permettre à un utilisateur d'accéder à ces données via
un mécanisme générique et nommé.

Par exemple si la classe A possède 2 fonctions f et g renvoyant l'une un
double et l'autre une chaîne, il doit pouvoir accéder à ces
informations.
On doit lui présenter "f" et "g".

Ensuite, une fois qu'il a sélectionné ou bien "f" ou bien "g", il faut
être en mesure d'effectuer l'opération f() ou g(), et d'obtenir la
valeur.

Les valeurs sont gérées par une classe Variable non développée ici.
Je cherche à implanter ce que j'ai appelé un représentant de A, et
notament de A::f() et de A::g().

Je veux pouvoir représenter n'importe quoi, et obtenir un mécanisme
unique qui permette de traiter la valeur de retour, quel que soit ce qui
est représenté et le type de la valeur de retour.
Je veux que ce mécanisme soit simple à étendre, tant dans les classes à
représenter que dans les fonctions des classes représentées.

Le problème est simple dans la mesure où tout est connu à la
compilation, et qu'on ne représente que des données, donc cela revient à
une fonction sans argument.


Ce n'est pas encore hyper clair, mais c'est mieux. Je vais essayer de
reformuler, voir si j'ai bien compris.

Tu as une classe A :
struct A
{
Valeur f();
Valeur g();
};

Tu veux une classe RepresentantA
struct RepresentantA
{
RepresenantantA (A* vraieDonnee);
A* myA;
Valeur call(string name);
};

Et tu veux pourvoir écrire :

A a;
RepresentantA ra(&a);
Valeur v = ra.call("f"); // équivalent à Valeur v = a.f();

C'est bien ça ?

Si oui, j'aurais fait un truc du genre :

template<class Data> Representant
{
Represenant (Data * vraieDonnee);
Data* myData;
map<string, boost::function> myFunctions;
Valeur call(string name)
{
return myFunctions[name]();
}
}

struct RepresentantA : Representant<A>
{
RepresentantA(Data * vraieDonnee) : Representant<A>(vraieDonnee)
{
myFunctions["f"] = bind(myData, &A::f);
myFunctions["g"] = bind(myData, &A::g);
}
};


Je ne sais pas si ça peut aider, ou si je suis totalement à côté de la
plaque...

--
Loïc


Avatar
Stanislas RENAN
Repondre sur le NG wrote:
Ce n'est pas encore hyper clair, mais c'est mieux. Je vais essayer de
reformuler, voir si j'ai bien compris.

Tu as une classe A :
struct A
{
Valeur f();
Valeur g();
};

Tu veux une classe RepresentantA
struct RepresentantA
{
RepresenantantA (A* vraieDonnee);
A* myA;
Valeur call(string name);
};

Et tu veux pourvoir écrire :

A a;
RepresentantA ra(&a);
Valeur v = ra.call("f"); // équivalent à Valeur v = a.f();

C'est bien ça ?
Presque, oui.


ra doit être un pointeur sur classe de base afin de pouvoir utiliser
un représentant de façon "anonyme" :

RepresentantBase * rb = conteneur.getRepresentantReel();
Valeur v = rb.call("f");

getRepresentantReel() renvoyant une instance de RepresentantQQCH,
via un pointeur sur la classe de base, RepresentantBase ici.

Par ailleurs, la correspondance entre la représentation et la fonction
réelle doit être statique car elle est identique pour toutes les
instances d'un représentant, il est donc inutile de la construire pour
chaque instance.

Si oui, j'aurais fait un truc du genre :

template<class Data> Representant
{
Represenant (Data * vraieDonnee);
Data* myData;
map<string, boost::function> myFunctions;
Valeur call(string name)
{
return myFunctions[name]();
}
}

struct RepresentantA : Representant<A>
{
RepresentantA(Data * vraieDonnee) : Representant<A>(vraieDonnee)
{
myFunctions["f"] = bind(myData, &A::f);
myFunctions["g"] = bind(myData, &A::g);
}
};


Je ne sais pas si ça peut aider, ou si je suis totalement à côté de la
plaque...



J'ai l'impression que ce que tu proposes correspond à ce que je veux,
même si je n'en suis pas encore sûr car il faut que je potasse les
bibliothèques BOOST tout d'abord (et j'ai un peu de mal, je l'avoue, ça
n'est pas immédiat, d'autant plus que je ne connais pas la partie STL
dédiée à cela [bind, ptr_fun, etc.] ).

Voici le code qui compile et fonctionne, et qui n'utilise pas BOOST :

== // representant.cpp : Defines the entry point for the console application.
//


#include <map>
#include <set>
#include <string>
#include <iostream>

class Variable
{
public:
Variable(const std::string s) { std::cout << "Variable(" << s << ")"
<< std::endl; }
};

class Objet
{
public:
Variable * a() const { return new Variable("a"); }
Variable * b() const { return new Variable("b"); }
Variable * c() const { return new Variable("c"); }
};


// Functor de base
template <typename T>
class RepresentantFunctor
{
protected:
std::string m_name;
public:
RepresentantFunctor(std::string name) : m_name(name) {}
virtual Variable * operator()(const T& t) const { throw; }
bool operator<(const RepresentantFunctor<T>& t) const { return m_name
< t.m_name; }
const std::string& name() const { return m_name; }
};

// Représentant basic, pour l'interface car les types
// Representant<A> et Representant<B> n'ont aucun lien :(

class RepresentantIf
{
public:
virtual Variable * call(const std::string& name)=0;
virtual void doInitialize()=0;

static void init(RepresentantIf& t) { t.doInitialize(); }
};

// Représentant de base
template <typename T>
class Representant : public RepresentantIf
{
protected:
typedef std::map<const std::string, RepresentantFunctor<T> * /*,
cmpPtr<T> */> Fonctions;
typedef Fonctions::const_iterator const_iterator;
typedef Fonctions::iterator iterator;

T & m_instance;

private:
static Fonctions m_map;

protected:
Representant(T& inst) : m_instance(inst) {}

protected:
void associe(RepresentantFunctor<T>& f) { m_map[f.name()]=&f; }
const RepresentantFunctor<T>* recupere(const std::string& s) const
{
const_iterator it; it = m_map.find(s);
if (it != m_map.end())
return it->second;
else
return 0;
}

public:
virtual Variable * call(const std::string& name)
{
const RepresentantFunctor<T>* f = recupere(name);
return (f == 0) ? 0 : f->operator ()(m_instance);
}
};

// Functor dérivé A
class RepresentantFunctorA : public RepresentantFunctor<Objet>
{
public:
RepresentantFunctorA() : RepresentantFunctor<Objet>("A") {}
virtual Variable * operator()(const Objet& t) const { return t.a(); }
};

// Functor dérivé B
class RepresentantFunctorB : public RepresentantFunctor<Objet>
{
public:
RepresentantFunctorB() : RepresentantFunctor<Objet>("B") {}
virtual Variable * operator()(const Objet& t) const { return t.b(); }
};

// Représentant dérivé
class RepresentantObjet : public Representant<Objet>
{
//private:
public:
static RepresentantFunctorA s_rfa;
static RepresentantFunctorB s_rfb;
protected:
/* virtual */ void doInitialize()
{
associe(s_rfa);
associe(s_rfb);
}
public:
RepresentantObjet(Objet& instance) : Representant<Objet>(instance) {}
};

RepresentantFunctorA RepresentantObjet::s_rfa;
RepresentantFunctorB RepresentantObjet::s_rfb;
Representant<Objet>::Fonctions Representant<Objet>::m_map;

int main(int argc, char* argv[])
{

Objet o;
RepresentantIf * r = new RepresentantObjet(o);
RepresentantIf::init(*r); // appelé une unique fois en début de
// programme.
RepresentantObjet::s_rfa(o);
RepresentantObjet::s_rfb(o);
Variable * var = r->call(std::string("A")); // la substantifique
moëlle : on
// appelle "A" sur l'objet dont on a le représentant pointé par r,
var = r->call(std::string("B")); // ici, on appelle "B"

return 0;
}
==
le problème de virtuelle non appelée était dû à une erreur de ma part.
J'avais un set d'objets de base, et je copiais dans ces objets des
instances de classe dérivée. Il était donc normal que la fonction
appelée fût celle de base !

Du fait de l'utilisation de pointeurs, je suis "obligé" de passer par
une map pour faire les comparaisons sur les clefs. J'ai essayé de passer
une fonction de comparaison de mon cru (en gros une spécialisation de
less<T>(T cons * const X, T const * const Y) pour les pointeurs, qui
compare *X et *Y au lieu de X et Y, mais du coup ça m'obligerait à
construire un T temporaire avec la bonne chaîne pour utiliser
set::find().
Finalement, je préfère une map, plus simple.

Le code ainsi écrit est assez difficile à lire et j'ai du mal à le
manipuler pour l'instant (et dire que c'est moi qui l'ai écrit, je n'ose
pas imaginer pour les autres).

Je trouve ta version beaucoup plus élégante... a priori (plus courte,
plus simple, plus lisible, plus facile à étendre).
Malheureusement, le problème, c'est qu'elle ne compile pas et que pour
l'instant j'ai plutôt des erreurs de syntaxe et des internal compiler
errors.

Comeau me dit par ailleurs que mon typedef template est erroné lorsque
je compile en mode strict.

J'ai pour l'instant modifié ton code en :

== #include <boost/bind.hpp>
#include <boost/function.hpp>
#include <functional>

#include <map>
#include <string>

struct Valeur {};

struct A
{
Valeur f();
Valeur g();
};

template<class Data>
class Representant
{
protected:
Representant (Data * vraieDonnee) : myData(vraieDonnee) {}
Data* myData;
std::map<std::string, boost::function0<Valeur *> > myFunctions;
public:
Valeur * call(std::string name)
{
return myFunctions[name]();
}
};

struct RepresentantA : Representant<A>
{
RepresentantA(A * vraieDonnee) : Representant<A>(vraieDonnee)
{
myFunctions["f"] = boost::bind(myData, &A::f);
myFunctions["g"] = boost::bind(myData, &A::g);
}
};

int main()
{
A a;
RepresentantA ra(&a);
Valeur * v = ra.call("f"); // équivalent à Valeur v = a.f();

return 0;
}
==
mais ça explose à la compilation avec 1 million d'erreurs sur bind()...
--
Stanislas RENAN