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

Rédéfinition de l'opérateur + dans toute une hierarchie de classes

13 réponses
Avatar
Marc
Bonjour,
j'ai une hierarchie de classes définie par exemple comme suit :

CBase
|
----------------------------------------------------
| | |
etc...
CType1 CType2 CType3
|
-----------------
| |
CType31 CType32

Je voudrais redéfinir l'opérateur + de telle sorte que je puisse additionner
tous les types à partir de pointeurs/références sur la classe de base.

CBase {
...
virtual CBase& operator+(const CBase&);
...
};

CType1 {
...
virtual CBase& operator+(const CBase&);
CBase& operator+(const CType2&);
CBase& operator+(const CType3&);
...
};
etc...
Le problème, c'est que lorsque je fais
CBase &type1=CType1();
CBase &type2=CType2();
type1+type2;
je détermine bien dynamiquement l'opérande gauche de l'opérateur (définition
virtuelle de celui-ci), mais pas l'opérande droite, qui doit me permettre
d'appeler l'implémentation appropriée de l'opérateur !
Je suis donc obligé de faire un switch intégral dans la fonction
virtual CBase& CType1::operator+(const CBase&)
pour déterminer le type de l'opérande droite et appeler la fonction
appropriée ! (évidement, ça permet quand même de détecter le type d'addition
demandé et de vérifier qu'elle est bien définie dans la classe si jamais on
ajoute d'autres classes à la hierarchie, ce qui est quand même plus
prudent..).
Ma question : y-a-il un moyen d'éviter ceci !? Je n'ai pas trouvé...
Merci

10 réponses

1 2
Avatar
Fabien LE LEZ
On Sun, 18 Jan 2004 11:30:49 +0100, "Marc" wrote:

CBase &type1=CType1();


C'est bizarre que ton compilo n'ait pas ralé : "CType1()" crée un
temporaire, et la référence "type1" ne pointe plus sur rien après
cette ligne.

Une écriture correcte serait :

CType1 mon_objet_type1;
CBase& type1= mon_objet_type1;

virtual CBase& operator+(const CBase&);


Pas bon non plus : operator+ crée un nouvel objet, renvoyer une
référence est donc une erreur.

Prenons une hiérarchie un peu plus simple : une classe "Base", et des
classes dérivées A et B :

Base
__|__
| |
A B

Si on peut ajouter un A et un B, de quel type est le résultat ?

--
;-)

http://www.gotw.ca/gotw/063.htm
http://www.gotw.ca/gotw/067.htm#2

Avatar
Sylvain Togni

Bonjour,
j'ai une hierarchie de classes définie par exemple comme suit :

CBase
|
----------------------------------------------------
| | |
etc...
CType1 CType2 CType3
|
-----------------
| |
CType31 CType32

Je voudrais redéfinir l'opérateur + de telle sorte que je puisse
additionner

tous les types à partir de pointeurs/références sur la classe de base.


Le pattern du « double dispatch » résoud ce genre de problème.
L'astuce consiste, dans la fonction virtuelle operator+, à appeler
une seconde fonction virtuelle, mais cette fois-ci sur l'argument x.

class CBase
{
public:
virtual CBase& operator+(const CBase&) const = 0;

protected:
virtual CBase& addFromType1(const CType1&) const = 0;
virtual CBase& addFromType2(const CType2&) const = 0;
// ...
};

class CType1 : public CBase
{
public:
virtual CBase& operator+(const CBase& x) const
{
return x.addFromType1(*this);
}

protected:
virtual CBase& addFromType1(const CType1& x) const
{
// ...
}
virtual CBase& addFromType2(const CType2& x) const
{
// ...
}
// ...
};

Sylvain

Avatar
kanze
"Marc" wrote in message
news:<400c2d71$0$7156$...

j'ai une hierarchie de classes définie par exemple comme suit :

CBase
|
----------------------------------------------------
| | |
etc...
CType1 CType2 CType3
|
-----------------
| |
CType31 CType32

Je voudrais redéfinir l'opérateur + de telle sorte que je puisse
additionner tous les types à partir de pointeurs/références sur la
classe de base.


Et qu'est-ce qu'il doit renvoyer ? Un opérateur +, au moins d'en abuser,
renvoyer un nouvel objet. Par valeur. Quel doit être le type de cet
objet ?

CBase {
...
virtual CBase& operator+(const CBase&);


Tu renvoies une référence. À quoi ? La sémantique normale de + exige un
nouvel objet. Où est-ce que tu le mets si tu en renvoies une référence.

...
};


En général, les opérateurs arithmétiques et la sémantique de référence
ne fonctionnent pas bien ensemble en C++. Et par défaut, le
polymorphisme dynamique exige une sémantique de référence.

En fait, s'il s'agit d'une hièrarchie de types arithmétiques, c'est
précisemment le problème auquel Coplien s'adresse avec l'idiome de
lettre/envellope. Il y traite aussi la question du « double dispatch »
(c-à-d que la fonction réelement appelée dépend des deux types, et non
seulement d'un). Si tu cherches à faire quelque chose de pareil, je te
conseille très fort de le lire d'abord. (« Advanced C++ Programming
Styles and Idioms », James Coplien, Addison-Wesley, ISBN 0-201-54855-0.
Il n'est pas tout récent, mais pour ton problème, je ne connais pas de
mieux plus récent.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
Marc
CBase &type1=CType1();
C'est bizarre que ton compilo n'ait pas ralé : "CType1()" crée un

temporaire, et la référence "type1" ne pointe plus sur rien après
cette ligne.


Mon compilo n'a pas calé parce que je ne lui ai pas soumis ce code...
Bon, j'ai plutôt l'habitude de manipuler des pointeurs et donc d'écrire qlq
chose du genre
CBase *p=new CType1();
ce qui est parfaitement correct...
J'avoue que je trouve dommage que l'écriture
CBase &type1=CType1();
ne soit pas acceptée.
C'est parce que le constructeur ne retourne aucun objet ? et que CType1()
n'étant pas "nommé", il est créé puis détruit ?
sinon, pour ce qui est des règles de portée, cette écriture ne devrait pas
poser de problème...

virtual CBase& operator+(const CBase&);
Pas bon non plus : operator+ crée un nouvel objet, renvoyer une

référence est donc une erreur.


là, j'avoue que le & est une faute d'étourderie

Base
__|__
| |
A B

Si on peut ajouter un A et un B, de quel type est le résultat ?


En fait ma classe de base ne me sert que pour déclarer des méthodes communes
virtuelles "supportées" par toutes les autres.
Par exemple :
A est un entier
B est un vecteur
B+A retourne un vecteur
A+B lance une exception
Je développe un interpréteur et cette organisation me facilite grandement
l'analyse contextuelle à partir de l'arbre syntaxique.
Le pattern du « double dispatch » proposé par Sylvain Togni résoud mon
problème.
Excuse-moi si je n'ai pas été clair et assez approximatif dans ma syntaxe.
Merci.


Avatar
Marc
Merci pour tes explications,
c'est exactement la réponse qu'il me fallait.
En me creusant les neuronnes, j'aurais sans doute pu trouver une astuce dans
le genre mais je dois dire qu'un tel code perd quand même en lisibilité !
Comme dans chaque classe, il faut bien sûr systématiquement redéfinir
l'opérateur pour toutes les combinaisons possibles des types, n'est t-il pas
plus simple (et au moins aussi performant) de faire un switch(...), même si
c'est moins élégant ?
Qu'en penses-tu ?
Marc
Avatar
Fabien LE LEZ
On Tue, 20 Jan 2004 14:03:26 +0100, "Marc" wrote:

J'avoue que je trouve dommage que l'écriture
CBase &type1=CType1();
ne soit pas acceptée.


Dans quel(s) cas verrais-tu une utilité ?

Note : une des utilités de l'allocation dynamique est justement la
gestion du polymorphisme d'héritage, du coup l'écriture
CBase *p=new CType1();
est assez courante.

--
;-)

http://www.gotw.ca/gotw/063.htm
http://www.gotw.ca/gotw/067.htm#2

Avatar
Michel Michaud
Dans news:, Fabien LE
Note : une des utilités de l'allocation dynamique est justement la
gestion du polymorphisme d'héritage, du coup l'écriture
CBase *p=new CType1();
est assez courante.


Pas en C++ :

CBase *p=new CType1;

fonctionne aussi normalement ! :-)

--
Michel Michaud
http://www.gdzid.com
FAQ de fr.comp.lang.c++ :
http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ/

Avatar
Fabien LE LEZ
On Tue, 20 Jan 2004 15:00:40 -0500, "Michel Michaud"
wrote:

CBase *p=new CType1;

fonctionne aussi normalement ! :-)


Argh, les pièges du copier-coller ! ~_~

--
;-)

http://www.gotw.ca/gotw/063.htm
http://www.gotw.ca/gotw/067.htm#2

Avatar
Gourgouilloult

un tel code perd quand même en lisibilité !


Pour ne pas trop surcharger, tu peux toujours ne pas définir tes
fonctions membres en inline (du point de vue géographique).

Comme dans chaque classe, il faut bien sûr systématiquement redéfinir
l'opérateur pour toutes les combinaisons possibles des types, n'est t-il pas
plus simple (et au moins aussi performant) de faire un switch(...), même si
c'est moins élégant ?


Je n'ai pas le sentiment que ça soit pareil. Quand tu passes dans un
truc comme :

CBase& CType1::operator+(const CBase& x) const
{
return x.addFromType1(*this);
}

dans l'appel x.addFromType1(*this), x est un objet considéré comme un
CBase du point de vue du compilo, mais à l'exécution, il «sait» à quelle
classe il appartient effectivement. Classe qui (schématiquement) dispose
d'un tableau associant à chaque fonction membre virtuelle une définition
précise. Donc x.addFromType1() est résolu en complexité constante. (1)

A l'opposé, si tu utilises un switch, tu vas avoir une suite de tests en
complexité linéaire. C'est à dire que chaque fois que tu rajoutes un
type à la hiérarchie, tu rajoutes un test supplémentaire, qui prendra
potentiellement plus de «temps» à l'exécution, contrairement à la
solution avec héritage.

Une réponse plus fervente serait de s'en tenir à dire qu'une des façons
de faire « est C » et l'autre « est C++ » ;)

(1) Euh... en fait, je trouverais logique que ça marche bien comme ça,
mais essentiellement dans le sens où on n'a pas de surcharge de
X::addFromType1(), quelque soit la classe X de la hiérarchie.

Comme je ne me souviens pas avoir étudié la question de la résolution de
surcharge de fonctions virtuelles (2), y a-t-il un pro qui pourrait
brièvement m'éclairer ? Est-ce que ça passe tout simplement (par
exemple, si on avait nommé les fonctions sur la base de :
CBase::addFromOtherType (CType1 & const) const
CBase::addFromOtherType (CType2 & const) const

(Ca ne me semble pas impossible non plus dans ce cas précis, puisque
dans l'expression « x.addFromOtherType (*this) », this a toujours un
type bien connu. Mais ça reste un cas particulier... et je ne vois pas
bien ce qui arrive à mon histoire de complexités.)

(2) En fait, j'suis pas très calé en résolution de surcharges tout
court. D'où une deuxième question, plus classique : j'ai pas dit trop de
bêtises ?

Marc


Gourgouilloult du Clapotis

Avatar
Gourgouilloult
CBase& CType1::operator+(const CBase& x) const


Oui bon, en omettant la question du retour par référence, on va dire. ;)
(En fait, je me suis plus intéressé à la façon de descendre dans la pile
d'appels, pas à ce qu'on en remonte.)

1 2