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
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.
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)
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.
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)
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.
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)
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
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
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
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 ».
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 ».
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
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 :
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() {}
(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
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 :
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() {}
(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.
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 :
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() {}
(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
Marc
Merci pour ta réponse détaillée qui répond bien à mon problème. J'ai choisi la deuxième solution... Marc
Merci pour ta réponse détaillée qui répond bien à mon problème.
J'ai choisi la deuxième solution...
Marc
Merci pour ta réponse détaillée qui répond bien à mon problème. J'ai choisi la deuxième solution... Marc
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
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)...
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
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...
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
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...
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 ;-)
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 ;-)
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 ;-)
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)
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)
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)
kanze
"Marc" wrote in message news:<417e5bd0$0$28673$...
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
"Marc" <metrica@free.fr> wrote in message
news:<417e5bd0$0$28673$636a15ce@news.free.fr>...
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
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