OVH Cloud OVH Cloud

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>

5 réponses

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

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.


Ben, la documentation, c'est pas pour les chiens.
Si les autres développeurs veulent écrire une classe dérivée
de Base, il faudra qu'ils se contentent de l'implantation
de Base, ou qu'ils réfléchissent, et lisent ta doc.

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


Sauf que comme b est de type statique Base, il recherche
dans les fonctions Base::compare, et il ne voit pas
la surcharge Fille::compare(Fille&,bool).


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

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


Je ne crois pas que tu puisses t'en sortir sans un
appel dynamic_cast ou type_id...
C'est un double dispatch, et c'est pas immédiat à faire
en C++. En fait, il te faut se baser sur le type dynamique
des deux arguments. Et avec les fonctions virtuelles, on a
accès qu'à l'un des deux, et on oublie l'autre. Pour
avoir les deux, il faut un dynamique_cast ou un type_id.

Sinon, c'est l'idée de la solution déjà présentée, à
raffiner en fonction de ce que tu veux faire (quand on
compare une Base et une Fille, que veux-tu ? )

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
Isidor.Ducasse
Sinon, c'est l'idée de la solution déjà présentée, à
raffiner en fonction de ce que tu veux faire (quand on
compare une Base et une Fille, que veux-tu ? )



En effet, je reste sur la première idée avec un dynamic_cast dans
chaque méthode de classe dérivée.

Pour l'aspect "comparaison", j'ai parlé "d'affinement", c'est à dire
qu'on s'en tient à comparer les objets au niveau hiérarchique le plus
spécialisé où les 2 objets sont de même sorte. Donc comparer un D1 e t
un D2, revient dans mon problème à comparer 2 Base, mais ça pourrait
être autre chose. Car je ne voulais pas être trop précis sur
l'objectif de la méthode. ça m'interesse dans un contexte général.
Mais, comme l'a deviné James, compare est plus proche d'une égalité
que d'un ordre (j'ai appelé la méthode Match() dans mon code).

Avatar
Isidor.Ducasse
On 22 nov, 23:11, Sylvain wrote:
wrote on 22/11/2007 10:34:

Je souhaite comparer des objets de classes différentes (mais [...]


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



Tout à fait, un bon conteneur de référence standard serait bien
agréable!


Avatar
Isidor.Ducasse
C'est une proposition intéressante.
Ta méthode cost() me rappelle un peu une fonction de hashage qui
conserverait l'ordre...
Mais, il y a des chances que le calcul de cost() soit cher si les
objets commencent à être un peu gros...
Avatar
Michel Decima

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


Coincidence: hier j'ai eu besoin d'ecrire une telle classe,
avec l'idee de m'en servir en tant que clé dans une map.

J'ai défini l'opérateur < betement, c'est a dire qu'il ne faisait
qu'appeler std::type_info::before(). J'ai quand meme eu un doute
sur le moment, en voyant que les tests d'egalité de std::type_info
sont déclarés sous forme d'opérateur, alors que la relation d'ordre
se nomme before() et non operator<(), mais je n'ai pas cherché plus
loin.

Ce matin, j'utilise cette classe sur une autre plateforme, et je
constate des doublons dans la map... tout ca parce que les type_info
d'une meme classe obtenus dans deux unites de compilation ne sont pas
identiques : ils n'ont pas la meme adresse, la comparaison avec
operator== retourne true, mais avec before() on obtient false dans les
deux sens...

En relisant la doc, je vois que ce comportement est parfaitement
autorisé, et je comprends pourquoi la fonction de comparaison
se nomme before() et pas operator<().

Un grand merci a toi pour la formulation "opérateur defini comme il
faut", ca m'a permi de trouver le probleme beaucoup plus vite ;)
(ca m'apprendra à lire la doc sans la comprendre...)

Pour ceux que ca peut interesser: j'ai constaté des type_info identiques
dans des unites de compilation differentes sur x86/linux/g++-4.1.2 et
ia64/hpux/aCC, et des type_info distincts sur power/AIX/xlC-6.

Voila, désolé de raconter ma vie, mais bon, si ca peut servir a quelqu'un..

MD.

1 2