Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

pb: polymorphisme, signature, comparaison d'objets

15 réponses
Avatar
Isidor.Ducasse
Bonjour,

Je souhaite comparer des objets de classes diff=E9rentes (mais
appartenant tous =E0 une hi=E9rarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets =E0 comparer sont de la m=EAme classe, alors la comparaison
doit prendre en compte les attributs sp=E9cifiques pour affiner la
comparaison.
-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes d=E9riv=E9es.
-- les classes d=E9riv=E9es doivent rest=E9es ind=E9pendantes entre elles.

Je voudrais utiliser un appel par virtualit=E9 pour toucher la m=E9thode
C::Compare(const C& ref) d=E9finie dans chaque classe, mais comme les
signatures diff=E8rent, il n'y a pas de surcharge et donc =E7a appelle
toujours la m=E9thode au niveau de la base. Si je surcharge correctement
(cf. classe D2 du code ci-dessous), je n'ai alors plus acc=E8s aux
donn=E9es sp=E9cifiques pour l'objet pass=E9 en argument.

Quelqu'un aurait-il une solution =E9l=E9gante?

J'imagine ce probl=E8me classique: tri d'un std::list<T*> avec codage
d'un foncteur de comparaison, mais je bute dans mes recherches.

ci-dessous un bout de code qui pr=E9sente le probl=E8me:
//<code>
#include <cstdio>
#include <cstdlib>

class Base
{
int m_Base;
public:
Base()
: m_Base(0)
{
}
virtual void Compare(const Base & ref) const
{
printf("Base m_Base %d %d\n", m_Base, ref.m_Base);
}
};

class D1 : public Base
{
int m_D1;
public:
D1()
: Base(),
m_D1(1)
{
}

virtual void Compare(const D1 & ref) const
{
Base::Compare(ref);
printf("D1 m_D1 %d %d\n", m_D1, ref.m_D1);
}
};

class D2 : public Base
{
int m_D2;
public:
D2()
: Base(),
m_D2(2)
{
}
virtual void Compare(const D2 & ref) const
{
Base::Compare(ref);
printf("D2 m_D2 %d %d\n", m_D2, ref.m_D2);
}
virtual void Compare(const Base & ref) const
{
// ref etant de Base, je ne peux pas utilise ref.m_D2
printf("D2 surcharge Base::Compare ref.m_D2 non utilisable
\n");
}
};

int main(void)
{
Base base_1, base_2;
D1 d1_1, d1_2;
D2 d2_1, d2_2;

Base* a_1[] =3D { &base_1, &d1_1, &d2_1 };
Base* a_2[] =3D { &base_2, &d1_2, &d2_2 };

int nbElt_1=3D sizeof(a_1)/sizeof(Base*);
int nbElt_2=3D sizeof(a_2)/sizeof(Base*);

for (int iE=3D0; iE<nbElt_1; iE++)
{
for (int yE=3D0; yE<nbElt_2; yE++)
{
printf("%d,%d:", iE, yE);
a_1[iE]->Compare( (*a_2[yE]) );
}
}

return EXIT_SUCCESS;
}

// resultat
0,0:Base m_Base 0 0 // Base vs Base
0,1:Base m_Base 0 0 // Base vs D1
0,2:Base m_Base 0 0 // Base vs D2
1,0:Base m_Base 0 0 // D1 vs Base
1,1:Base m_Base 0 0 // D1 vs DI NOK, je voudrais un
appel =E0 D1::Compare
1,2:Base m_Base 0 0 // D1 vs D2 OK
D2 surcharge Base::Compare ref.m_D2 non utilisable // D2 vs Base
D2 surcharge Base::Compare ref.m_D2 non utilisable // D2 vs D1
D2 surcharge Base::Compare ref.m_D2 non utilisable // D2 vs D2

//</code>

10 réponses

1 2
Avatar
Marc Boyer
On 2007-11-22, wrote:
Bonjour,

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.
-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.
-- les classes dérivées doivent restées indépendantes entre elles.


Ben, sans un dynamic_cast, je ne vois pas comment faire.

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)

Avatar
Aris
On 2007-11-22, wrote:
Bonjour,

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.
-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.
-- les classes dérivées doivent restées indépendantes entre elles.


Ben, sans un dynamic_cast, je ne vois pas comment faire.

Marc Boyer
bon dans la classe de base, tu as une methode

virtual int compare(Base &b);

ensuite dans le classe Fille : public Base {
virtual int compare(Base &b){
Fille *f=<dynamic_cast>(Fille *)&b;
if(f!=null){
// continuer la comparaison
} else return -1; // différent
}


dans la classe fille je n'ai utilisé que la définition de fille, pas les
autres.


Avatar
Michel Decima

Fille *f=<dynamic_cast>(Fille *)&b;


Je ne connais pas cette ecriture. Ca ne serait pas plutot ca:

Fille* f = dynamic_cast< Fille* >( &b );

Avatar
Isidor.Ducasse
Fille* f = dynamic_cast< Fille* >( &b );
est en effet la bonne écriture. Le principe est bon et fonctionne, je

vous remercie.
J'y ai aussi pensé, et implémenter cette solution faute de mieux (car
ça ne me branche pas d'avoir une telle méthode avec un dynamic_cast
dans chaque classe dérivée, sachant que d'autres développeurs seront
peut-être amener à ajouter des classes dérivés et devront piger
l'intention.

J'ai une autre idée mais que je n'arrive pas à concrétiser: l'idée
consiste à inverser le sens d'appel A.compare(B) en B.compare(A) en
étant dans une méthode de la classe Fille: cela ressemblerais un peu à
ça
class Base
{
virtual int compare(Base &b) = 0;
virtual int compare(Base &b, bool /* arg bidon */) =0;
};

class Fille : public Base
{
virtual int compare(Base &b)
{ return b.compare(*this, true) ; } // *this est de type
Fille

virtual int compare(Base &b, bool /* arg bidon */)
{ return -1; }

virtual int compare(Fille &f, bool /* arg bidon */)
{ return /* ... code! ... */ }
};



On 22 nov, 14:19, Michel Decima wrote:

Fille *f=<dynamic_cast>(Fille *)&b;


Je ne connais pas cette ecriture. Ca ne serait pas plutot ca:

Fille* f = dynamic_cast< Fille* >( &b );



Avatar
Michel Decima
Fille* f = dynamic_cast< Fille* >( &b );
est en effet la bonne écriture. Le principe est bon et fonctionne, je

vous remercie.
J'y ai aussi pensé, et implémenter cette solution faute de mieux (car
ça ne me branche pas d'avoir une telle méthode avec un dynamic_cast
dans chaque classe dérivée, sachant que d'autres développeurs seront
peut-être amener à ajouter des classes dérivés et devront piger
l'intention.

J'ai une autre idée mais que je n'arrive pas à concrétiser: l'idée
consiste à inverser le sens d'appel A.compare(B) en B.compare(A) en
étant dans une méthode de la classe Fille.


On dirait un 'Double Dispatch'. Mais dans mon souvenir, il faudrait
que toutes les classes filles soient connues de la classe de base.
Mais il y a peut etre des méthodes pour s'affranchir de cette contrainte
(il y a tout un chapitre la dessus dans "Modern C++ Design" d'Alexandrescu).

Ca devrait donner qqch comme ca:

#include <iostream>

class Base;
class DerivA;
class DerivB;

class Base {
public:
int compare( Base const& other ) const
{
return other.compareImpl( *this );
}

virtual int compareImpl( Base const& other ) const = 0;

virtual int compareWithA( DerivA const& ) const = 0;
virtual int compareWithB( DerivB const& ) const = 0;
};

class DerivA : public Base {
public:
virtual int compareImpl( Base const& other ) const
{ return other.compareWithA( *this ); }

virtual int compareWithA( DerivA const& ) const
{ std::cout << "compare A - A" << std::endl; return 0; }

virtual int compareWithB( DerivB const& ) const
{ std::cout << "compare A - B" << std::endl; return 0; }
};

class DerivB : public Base {
public:
virtual int compareImpl( Base const& other ) const
{ return other.compareWithB( *this ); }

virtual int compareWithA( DerivA const& ) const
{ std::cout << "compare B - A" << std::endl; return 0; }

virtual int compareWithB( DerivB const& ) const
{ std::cout << "compare B - B" << std::endl; return 0; }
};

int main() {
DerivA a;
DerivB b;

a.compare( b );
b.compare( a );
a.compare( a );
b.compare( b );
}


Avatar
Michael DOUBEZ
On 2007-11-22, wrote:
Bonjour,

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.
-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.
-- les classes dérivées doivent restées indépendantes entre elles.


Ben, sans un dynamic_cast, je ne vois pas comment faire.


Il y aurait une solution de type visitor qui permettrait de descendre
dans la hierarchie.

//prenons une structur en diamant, A<-B; A<-C; B,C<-D
class A;
class B;
class C;
class D;

//un visitor assez simple
struct Visitor
{
virtual void visit_A(A&)=0;
virtual void visit_B(B&)=0;
virtual void visit_C(C&)=0;
virtual void visit_D(D&)=0;
};

//notre hierarchie
//interface de visitation est protected
//compare est public et utilise hello() pour comparer
class A
{
bool compare(A& a);
protected:
virtual void hello(Visitor& v){v.visit_A(*this);}
};

class B: virtual public A
{
protected:
virtual void hello(Visitor& v){v.visit_B(*this);}
};

class C: virtual public A
{
protected:
virtual void hello(Visitor& v){v.visit_C(*this);}
};

class D: public B, public C
{
protected:
virtual void hello(Visitor& v){v.visit_D(*this);}
};


Puis pour faire la comparaison:

//visitor pour comparer un objet de classe connu e T
// a un object de classe inconnue
template <class T>
struct VHalfComparator: public Visitor
{
VHalfComparator(T& t):lhs(t),result(false){}
T& lhs;
bool result;
void visit_A(A&);
void visit_B(B&);
void visit_C(C&);
void visit_D(D&);
};
//faire des specialisations de comparateur A-A, A-B, ...
//ceux-ci sont aappelés dans visit_T2<T1>(T2&)

//visitor pour comparer deux objets inconnus de base A
struct VComparator: public Visitor
{
VComparator(A& l,A& r):lhs(l),rhs(r),result(false)
{
lhs.visit(*this);
}
A& lhs;
A& rhs;
bool result;
void visit_A(A& a)
{
VHalfComparator<A> comp(a);
rhs.visit(comp);
result=comp.result;
}
void visit_B(B& b)
{
VHalfComparator<B> comp(b);
rhs.visit(comp);
result=comp.result;
}
void visit_C(C& c)
{
VHalfComparator<C> comp(c);
rhs.visit(comp);
result=comp.result;
}
void visit_D(D& d)
{
VHalfComparator<D> comp(d);
rhs.visit(comp);
result=comp.result;
}
};

Puis:
bool A::compare(A& a)
{
return VComparator(*this,a).result;
}

Il doit y avoir moyen d'utiliser les typelist et MPL pour automatiser
pas mal de choses.

Michael


Avatar
Michael DOUBEZ
On 2007-11-22, wrote:
Bonjour,

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.
-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.
-- les classes dérivées doivent restées indépendantes entre elles.


Ben, sans un dynamic_cast, je ne vois pas comment faire.


Il y aurait une solution de type visitor qui permettrait de descendre
dans la hierarchie.

//prenons une structur en diamant, A<-B; A<-C; B,C<-D
class A;
class B;
class C;
class D;

//un visitor assez simple
struct Visitor
{
virtual void visit_A(A&)=0;
virtual void visit_B(B&)=0;
virtual void visit_C(C&)=0;
virtual void visit_D(D&)=0;
};

//notre hierarchie
//interface de visitation est protected
//compare est public et utilise hello() pour comparer
class A
{
bool compare(A& a);
protected:
virtual void hello(Visitor& v){v.visit_A(*this);}
};


Il faut rendre VComparator friend de A pour pouvoir utiliser hello()
dans VComparator.

Sinon rendre hello() public, dans ce cas là, compare() pourrait être
sortie de l'interface (ça évitera une dépendance de plus) et à remplacer par

bool deep_compare(A& lhs,A&rhs)
{
return VComparator(lhs,rhs).result;
}

A noter que ça veut dire qu'un changement dans la hierarchie obligera à
tracer et modifier les visitors existant. C'est pour eviter l'inflation
de visitor que je voulais faire une visiation protégée.


Michael



Avatar
Sylvain
wrote on 22/11/2007 10:34:
Bonjour,

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.


ah qu'un tableau de réfrences serait agréable ...

-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.


imho cette hypothèse diaise l'ensemble, si les 2 instances
n'appartiennent pas à la même classe, chacune devrait également utiliser
ses champs propres pour s'évaluer; penser que la comparaison est
immédiate pour un cas (même classe) et pose un mystère pour le reste (2
classes différentes) montre que le problème est mal posé.

-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.


principe générique, bien sur attendu ici.

-- les classes dérivées doivent restées indépendantes entre elles.


quant à la finalité, implémentation, etc, mais quid de leu comparaison?

ayant:

Base <- - - D1
-- D2

un D1 est censé être plus ou moins qu'un D2 ?
en terme de virtualité cela ne signifie rien du tout.

vous pouvez revenir à quelque chose de pratique (et trivial) en vous
dotant d'une fonction cout [1] servant à évaluer les instances - si
votre classement est "plus subtil" cela ne conviendra peut être pas,
mais la subtilité devra être décrite avant d epouvoir être prise en compte.

Sylvain.

[1] par exemple -- la methode cost est ici une simple illustration,
votre problème est justement de la définir.


class Base {
private:
int _base;
public:
Base() : _base(0) {}
friend int operator < (const Base& a, const Base& b){
int costA = a.cost();
int costB = b.cost();
return (costA == costB) ? 0 : (costA < costB) ? -1 : 1;
}
protected:
virtual int cost() const { return _base; }
};

class D1 : public Base {
private:
int _d1;
public:
D1() : Base(), _d1(1) {}
protected:
virtual int cost() const { return _d1 * 10 + Base::cost(); }
};

class D2 : public Base {
private:
int _d2;
public:
D2() : Base(), _d2(2) {}
protected:
virtual int cost() const { return _d2 * 10 + Base::cost(); }
};

int main()
{
Base base_1, base_2;
D1 d1_1, d1_2;
D2 d2_1, d2_2;

Base* a_1[] = { &base_1, &d1_1, &d2_1 };
Base* a_2[] = { &base_2, &d1_2, &d2_2 };

int nbElt_1 = sizeof(a_1) / sizeof(Base*);
int nbElt_2 = sizeof(a_2) / sizeof(Base*);

for (int iE=0; iE<nbElt_1; iE++)
for (int yE=0; yE<nbElt_2; yE++)
printf("%d, %d: %dn", iE, yE, (*a_1[iE] < *a_2[yE]));
return 0;
}

Avatar
Aris

Fille *f=<dynamic_cast>(Fille *)&b;


Je ne connais pas cette ecriture. Ca ne serait pas plutot ca:

Fille* f = dynamic_cast< Fille* >( &b );

oui désolé. je ne pratique plus très souvent le C++ dont les détails

techniques comme la syntaxe m'échappent un peu


Avatar
James Kanze
On Nov 22, 10:34 am, wrote:

Je souhaite comparer des objets de classes différentes (mais
appartenant tous à une hiérarchie commune) en parcourant un conteneur
qui voit les objets comme des pointeurs sur la base.
-- Si 2 objets à comparer sont de la même classe, alors la comparaison
doit prendre en compte les attributs spécifiques pour affiner la
comparaison.


Et si les deux objets n'ont pas le même type dynamique ?

-- La classe de base et le conteneur ne doivent pas avoir connaissance
des classes dérivées.
-- les classes dérivées doivent restées indépendantes entre elles.


Si on accepte qu'il n'y a jamais égalité quand les types
dynamiques sont différents, la solution est assez simple :

class Base
{
public:
bool isEqual( Base const& other ) const
{
return typeid( *this ) == typeid( other )
&& doIsEqual( other ) ;
}

private:
virtual bool doIsEqual( Base const& other ) const = 0 ;
} ;

Dans chacune des classes dérivées, doIsEqual commence en
convertissant la reference à other en une référence à son propre
type :

bool
Derived::doIsEqual(
Base const& other ) const
{
assert( typeid( *this ) == typeid( other ) ) ;
Derived const& o
= static_cast< Derived const& >( other ) ;
return attr1 == o.attr1 && attr2 == 0.attr2 /* etc. */ ;
}

(Note que j'utilise une assertion et un static_cast, l'égalité
de type étant une précondition de l'appel à doIsEqual.)

Alternativement, on peut reproduire le test du type dans chacune
des classes filles :

bool
Derived::isEqual(
Base const& other ) const
{
Derived const* po
= dynamic_cast< Derived const* >( &other ) ;
return po != NULL
&& attr1 == po->attr1 /* etc */ ;
}

Note que la sémantique dans les deux cas est légèrement
différente aussi.

Si on veut aussi prévoir des cas où on peut avoir égalité entre
deux classes dérivées différentes (le plus souvent le cas), en
revanche, on est amené à utiliser une des techniques de
l'aiguillage double (<< double dispatch >>, en anglais). Qui,
pour la plupart, exige une connaissance de toutes les classes
dérivées dans la classe de base. Bien que... On pourrait
imaginer quelque chose du genre :

typedef std::pair< std::type_info const*,
std::type_info const* >
TypePair ;
struct TypePairCmp
{
bool operator()(
TypePair const& lhs,
TypePair const& rhs ) const
{
return lhs.first->below( *rhs.first )
|| ! ( rhs.first->below( *lhs.first )
&& lhs.second->below( *rhs.second ) ) ;
}
} ;
typedef std::map< TypePair,
bool (*)( Base const&, Base const& ) >,
TypePairCmp >
CmpMap ;

CmpMap Base::cmpMap ;


bool
Base::isEqual( Base const& other ) const
{
return typeid( *this ) == typeid( other )
? doIsEqual( other )
: tryMappedIsEqual( other )
}

bool
Base::tryMappedIsEqual( Base const& other ) const
{
CmpMap::const_iterator
entry
= cmpMap.find( std::make_pair( &typeid( *this ),
&typeid( other ) ) ) ;
return entry != cmpMap.end()
&& (*entry.second)( *this, other ) ;
}

(Chez moi, j'ai une petite classe wrapper pour les
std::type_info const*, avec l'opérateur < défini comme il faut.
Du coup, en utilisant cette classe dans le std::pair, son
operateur < marche d'office.)

Il te faut ensuite des fonctions du genre :

bool
compareD1D2( Base const& lhs, Base const& rhs )
{
assert( typeid( lhs ) == typeid( D1 )
&& typeid( rhs ) == typeid( D2 ) ) ;
return static_cast< D1 const& >( lhs ).isEqual(
static_cast< D2 const& >( rhs ) ) ;
}

Et évidemment, il faut en mettre les adresses dans le map.

--
James Kanze (GABI Software) email:
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