OVH Cloud OVH Cloud

operateur + comme fonction virtuelle pure dans une classe de base

33 réponses
Avatar
Marc
Bonjour, mon compilateur me refuse la définition de classe suivante :

class CTypeBase {

public :

CTypeBase() : _ismissing(true) {}
CTypeBase(const CTypeBase &x) : _ismissing(x._ismissing) {}

const bool& is_missing(void) const { return _ismissing;}
void set_missing(void) { _ismissing=true;}

// opérateurs de base redéfinis

virtual const CTypeBase operator+(const CTypeBase&) const=0;

protected :
private :

bool _ismissing;

};

Il m'indique juste que la classe CTypeBase ne peut contenir des fonctions
pures.
Ah bon !? pourquoi ?
ça n'a aucun sens pour moi de définir l'opérateur + dans la classe de base !
Merci de vos lumières

10 réponses

1 2 3 4
Avatar
Arnaud Meurgues
Marc wrote:

Ben oui, mais non. Le polymorphisme ne marche qu'à travers des
références ou des pointeurs. Si l'on écrit
CTypeBase
ça veut dire que le type de l'objet est obligatoirement CTypeBase, et
non pas une classe dérivée ou une superclasse.


pourquoi ?


Bonne question. Je n'ai peut-être pas bien compris ce que tu voulais faire.

je peux tout à fait écrire
virtual const ClasseDeBase operator+(const ClasseDeBase &) const { return
ClasseDerivee();}


Tout-à-fait.

Ce qui m'intéresse, c'est que ce soit le bon operator+ qui soit sélectionné.


Vi. Mais :

class CTypeInt : public CTypeBase {
[...]

int _value;
};

En fait, je définis les opérateurs dans la classe de base, qui n'est plus
abstraite, même si je ne m'en servirai jamais.


Elle reste abstraite conceptuellement. Même si C++ ne la reconnaît pas
comme telle.

const CTypeBase CTypeInt::operator +(const CTypeInt &x) const
{
CTypeInt entier;
if (_ismissing || x._ismissing)
return entier;
entier._ismissingúlse;
entier._value=_value+x._value;
return entier;
}

J'espère que je reste compréhensible...


C'est compréhensible. Mais ta fonction retourne un CTypeBase et pas du
tout un CTypeInt. Et par conséquent, il n'y a aucune raison qu'il ait un
champ _value. Un CTypeBase n'a pas de champ _value et n'en aura jamais.
Le CTypeBase retourné n'aura pris que les infos propres à la partie
CTypeBase du CTypeInt, et rien de ce que CTypeInt a en plus.

Si tu veux pouvoir passer un (vrai) CTypeInt à travers la classe
abstraite de base CTypeBase, tu n'as pas d'autre choix que d'utiliser
une référence ou un pointeur (éventuellement via un proxy, ce qui est
sans doute similaire à la solution lettre/enveloppe proposée par Jean-Marc).

Par ailleurs, à chaque fois que tu vas vouloir enrichir la hiérarchie de
classe, tu serais amené, avec ton système, à modifier la class CTypeBase
pour déclarer l'operator+ qui va bien. En général, quand la création
d'une nouvelle classe dérivée impose la modification d'une classe de
base, c'est qu'il faut se poser des questions sur le design.

--
Arnaud
(Supprimez les geneurs pour me répondre)


Avatar
Marc
Eh bien malheureusement pour moi, je suis obligé de reconnaître que tu as
raison sur toute la ligne...
C'est vrai que j'utilise toujours le polymorphisme avec des pointeurs, et du
coup, j'ai voulu faire n'importe quoi...
Bon, la prochaine fois il faudra que je réflechisse plus avant d'ameuter le
quartier.
Merci pour tes lumières...
Marc
Avatar
drkm
Arnaud Meurgues writes:

Marc wrote:

Si l'on écrit
CTypeBase
ça veut dire que le type de l'objet est obligatoirement CTypeBase, et
non pas une classe dérivée ou une superclasse.


pourquoi ?


Bonne question. Je n'ai peut-être pas bien compris ce que tu voulais faire.


Ben un objet de type « CTypeBase » est un « CTypeBase ». Un
« CTypeBase & » peut référencer un objet de type dérivé, de même qu'un
« CTypeBase * » peut pointer sur un objet de type dérivé. Dans :

struct A {
} ;

struct B : A {
} ;

A f_1() {
static B b ;
return b ;
}

A & f_2() {
static B b ;
return b ;
}

void g() {
A a_1 = f_1() ;
A & a_2 = f_2() ;
}

« a_1 » est un « A », avec tous les problèmes de slicing. « a_2 »
référence un objet de type « B ».

--drkm



Avatar
Christophe Lephay
Marc wrote:
Eh bien malheureusement pour moi, je suis obligé de reconnaître que
tu as raison sur toute la ligne...
C'est vrai que j'utilise toujours le polymorphisme avec des
pointeurs, et du coup, j'ai voulu faire n'importe quoi...
Bon, la prochaine fois il faudra que je réflechisse plus avant
d'ameuter le quartier.


Tu as deux solutions pour résoudre ton problème. La première est le
double-dispatch. L'inconvénient du double-dispatch, c'est que la classe de
base doit connaitre toutes les différentes versions de ta fonction dans les
classes dérivées. Par contre par ce biais, tu y sais aussi de quel type est
l'objet que doit renvoyer chaque version :

class CTypeBase
{
...
public:

virtual CTypeInt operator+( const CTypeInt& ) = 0;
virtual CTypeDouble operator+( const CTypeDouble& ) = 0;
...
};

Chaque classe dérivée doit définir chacune des versions (quitte à renvoyer
une erreur/exception pour celles qui ne leurs sont pas applicables).


La deuxième façon, plus modulaire, consiste à utiliser l'idiome
lettre-enveloppe qu'évoque Jean-Marc. En gros, l'idée de cet idiome est que
la classe de base est bien concrète (il peut exister des objets de cette
classe), mais qu'elle contient un pointeur vers un objet d'une classe
dérivée auquel sont délégués les appels de fonction :

class CTypeBase
{
CTypeBase * objet_reel;

CTypeBase& Add( const CTypeBase& ) { return *this; } // ne fait rien
dans la classe de base

public:

CTypeBase( CTypeBase * p ) : objet_reel( p ) { assert( objet_reel ) !=
0; } // on s'assure que l'objet concret existe bel et bien (1)
virtual ~CTypeBase() {}

CTypeBase& operator+( const CTypeBase& rhs )
{ return objet_reel->Add( rhs ) ; }
};

class CTypeInt : public CTypeBase
{
int value;

CTypeBase& Add( const CTypeBase& rhs )
{
value += dynamic_cast< CTypeInt& >( rhs ).value; // (2)
return *this;
}
};

class CTypeDouble : public CTypeBase
{
double value;

CTypeBase& Add( const CTypeBase& rhs )
{
value += dynamic_cast< CTypeDouble& >( rhs ).value; // (3)
return *this;
}
};

(1) Dans la pratique, l'objet pointé par objet_reel n'est pas supposé d'être
d'une quelconque utilité en dehors de l'objet CTypeBase, et c'est à toi de
voir comment il convient de gérer sa durée de vie (par exemple en le
deletant dans le destructeur de CTypeBase).

(2)(3) Là encore, le code est extrèmement simplifié : il plantera si rhs
n'est pas du type CTypeInt dans (2) ou du CTypeDouble dans (3). A toi de
voir comment gérer au cas par cas (par exemple en levant une exception ou en
effectuant une conversion préalable.

Chris

Avatar
Marc
Merci pour ta réponse détaillée qui répond bien à mon problème.
J'ai choisi la deuxième solution...
Marc
Avatar
Christophe Lephay
Marc wrote:
Merci pour ta réponse détaillée qui répond bien à mon problème.


De rien...

J'ai choisi la deuxième solution...


Avant de te lancer dans le codage, je te conseille de faire des recherches
sur l'idiome enveloppe-lettre, mon exemple n'ayant eu pour but que de
t'expliquer l'idée générale (l'utiliser tel quel serait source de
problèmes)...

Chris

Avatar
Marc
Avant de te lancer dans le codage, je te conseille de faire des recherches
sur l'idiome enveloppe-lettre, mon exemple n'ayant eu pour but que de
t'expliquer l'idée générale (l'utiliser tel quel serait source de
problèmes)...
J'avais déjà implémenté ce modèle avec une classe "enveloppe" externe...

Ton modèle me pousse à chercher à mettre cette classe tout au sommet de la
hierarchie, ce qui facilite l'écriture et même l'efficacité à ce qu'il me
semble...
CTypeRoot // contient juste un pointeur vers un CTypeBase valide si c'est
une valeur
| // de retour de operator + et un pointeur NULL sinon
CTypeBase
|
CTypeInt

J'y travaille et ça avance...

Avatar
Alexandre
Il faut obligatoirement retourner une référence ou un pointeur (ici,
plutôt une référence).


sauf que pour un opérateur + c'est assez délicat ;-)

Avatar
Arnaud Meurgues
Alexandre wrote:

Il faut obligatoirement retourner une référence ou un pointeur (ici,
plutôt une référence).
sauf que pour un opérateur + c'est assez délicat ;-)



Effectivement. De fait, j'ai plutôt l'habitude d'utiliser la
redéfinition des opérateurs arithmétiques sur des classes à sémantique
de valeur qui, par essence, n'ont pas de polymorphisme.

Mais délicat ou non, il n'a pas le choix. A priori, il est obligé de
passer par un proxy.

--
Arnaud
(Supprimez les geneurs pour me répondre)


Avatar
kanze
"Marc" wrote in message
news:<417e5bd0$0$28673$...
virtual const CTypeBase& operator+(const CTypeBase&) const=0;


Je comprends ce que tu cherches à faire, mais à mon avis, il vaut
mieux retourner l'objet par valeur ! Tu retournes une référence sur
quoi ?


Tout à fait.

En général, le polymorphisme se combine assez mal avec une sémantique à
valeur, et les opérateurs binaires de type + ne se comporte d'une façon
habituelle avec une sémantique de valeur. Il y a des cas où il faut
combiner les deux, mais dans ces cas-là, la réponse est prèsque toujours
l'idiome lettre-envéloppe.

Voir Coplien pour tous les détails -- il traite un problème
prèsqu'identique (sinon identique) au tien.

--
James Kanze GABI Software http://www.gabi-soft.fr
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 3 4