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

classe abstraite / "java implement" / ...

14 réponses
Avatar
meow
Bonjour, d=E9sol=E9 pour le titre un peu vague mais je ne vois pas
comment d=E9crire mon probl=E8me en une phrase... Le plus simple c'est de
regarder le petit exemple ci dessous :

/*! A abstraite et donc non instenciable */
class A{ public:
la_fonction_que_je_dois_implementer_pour_etre_un_A()=3D0;
}

/*! AA instenciable */
class AA:public A{ public:
la_fonction_que_je_dois_implementer_pour_etre_un_A(){;};
}

/*! aggr=E8ge un A et donc ne compile pas */
B{
A _a;
B(A& a):_a(a){;}
}

Donc voil=E0 l'id=E9e : je cherche =E0 d=E9crire que B va aggreger un objet
qui contient une fonction
la_fonction_que_je_dois_implementer_pour_etre_un_A, sachant que
l'impl=E9mentation de cette fonction d=E9pendra de l'objet qu'on passera
au constructeur. A terme j'aurai AA:public A, mais aussi AB:public A,
etc... et je veux pouvoir aggr=E9ger indiff=E9rement un AA ou un AB dans
B=2E..
A priori si je retires le "=3D0" dans A =E7a se passe bien, mais =E0 ce
moment l=E0 je n'ai aucun moyen d'empecher l'utilisateur de me fournir
dirrectement un A, auqel cas la_fonction_... ne sera pas
impl=E9ment=E9e...

4 réponses

1 2
Avatar
James Kanze
meow wrote:
@pierre


Merci, en effet, ça fonctionne.
Franchement, je commence à me décourrager devant la somme de
connaissances à aquerrir avant de pouvoir coder un peu
proprement en C++... :(


On ne peut pas dire que c'est un langage tout simple. On
revanche, une fois qu'on le connaît bien, on remarque que les
programmes qu'on y écrit sont souvent beaucoup plus simple que
dans d'autres langages. La complexité du langage est le prix de
la simplicité des programmes.

Dans la mesure où j'ai à faire avec un langage, mais beaucoup de
programmes, c'est un investissement rentable.

La solution avec le pointeur me plait moins parce que B(A *a)
sous entend que l'objet A que je vais abriter dans B existe à
l'extérieur et y est donc modifiable. Le mieux serait que j'en
fasse une copie... genre :


class B{
A *_a;
B(A& a){_a=new A(a);};


non ?


Le problème, c'est de savoir ce que tu veux faire. Avant de
donner une solution, il faut que je sache le problème. Mais
attention : si A est un type abstrait (inachevé ou classe
abstraite), « new A » n'est pas permis. Parce que « new A »
crée toujours un objet de type A, jamais d'un type dérivé de A.

Si on veut copier un type polymorphique, l'idiome classique est
d'utiliser une fonction membre clone, e.g. :

class A
{
public:
A* clone() const
{
A* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

private:
virtual A* doClone() = 0 ;
} ;

Mais très souvent, on préfère ne pas supporter la copie du tout.
Dans ce cas-là, on declare et le constructeur de copie et
l'opérateur d'affectation privé, et on ne les implémente pas.
(Ou on dérive de boost::noncopiable.)

@kanze


Mais ce n'est pas ce que tu as écrit. Tu as écrit que tu veux
une copie d'un A, qui est en A -- si on te donne une dérivée
de A, c'est bien, mais tu ne va copier que la partie A de
cette dérivée.



Hum... Dans mon cas ce ne sera pas forcément problématique.
Néanmoins, c'est un problème que je n'avais pas vu. Que faire
?


Ça dépend du problème à résoudre. Typiquement, les objets
polymorphiques ne sont pas copiable, ou sinon, on les copie au
moyen d'une fonction clone() (et dans mon expérience, la copie
sert surtout dans la gestion des transactions, pour permettre un
roll-back).

Si on veut réelement supporter la copie et l'affectation des
objets polymorphiques, il faut utiliser l'idiome
lettre/envellope.

Tu as cité Java dans le sujet, alors... Si tu fais comme en
Java, et utilises un pointeur ou une référence, ça marche.



Je ne connais pas Java, je ne l'ai cité ici que parce que ceux
qui m'entourrent ici programment en java et qu'ils m'ont parlé
des interfaces... Un mécanisme qui semble se rapprocher de ce
que je veux faire.


Attention : l'« interface » en Java est un peu bizarre, et ne
correspond en rien au concepte d'interface dans le sens de la
programmation en général. (Tu ne pourrais pas declarer quelque
chose comme clone(), ci-dessus, en Java. Alors que c'est typique
de ce qu'on s'attend à une interface dans le sens général.)

Maintenant je ne vois pas bien en quoi l'utilisation du
pointeur résoud le problème précédent... Je veux dire, si _a
pointe vers l'extérieur de B comme l'a suggéré Pierre, OK,
l'objet pointé par _a peut etre d'une classe fille (encore
faudra t'il assaisonner a la sauce virtual, non ?), mais il
n'est pas "abrité" par B. Et si je fais mon micmac _a=new
A(a), j'obtiens bien un pointeur sur A et je fiche à la
poubelle les spécificités des filles.


Pour qu'un objet se comporte d'une façon polymorphique en C++,
il faut bien des fonctions virtuelles, oui. Pour commencer, il
faut que le destructeur soit virtuel -- au moins que tu utilises
un glaneur de cellules, et que tu n'as pas de destructeurs.
(C'est le cas pour mes classes « agents », par exemple.)
Ensuite, chaque fois que tu veux un comportement polymorphique
d'une fonction, tu déclares la fonction comme la fonction clone,
ci-dessus, c-à-d que tu définis une fonction publique que
enforce le contrat (pré- et post-conditions, invariantes), et
qui appelle une fonction privée virtuelle pour effectuer le
comportement ; la classe dérivée supplantera cette fonction
virtuelle. (Évidemment, dans les cas comme visiteur, où il n'y a
pas vraiment de contrat au niveau de la classe de base, on fera
la fonction publique virtuelle, et on se passera de la fonction
privée.)

Encore une fois, dans mon cas ça ne semble pas etre un
probleme dans la mesure où tout ce qui m'intéresse c'est la
fonction déclarée dans A et à définir dans les filles.


Je ne suis pas sûr, mais j'ai plutôt l'impression que ce que tu
cherches, c'est le modèle « stratégie ». C-à-d que tu as une
classe (ici B) avec un comportement général, que tu customises
selon le cas par un délégué -- un deuxième objet, polymorphique,
qui effectue la partie du travail qui peut varier selon la
customisation. Chez moi, par exemple, dans FieldArray -- une
classe qui présente une chaîne de caractères comme un tableau de
champs, avec customisation sur la façon de délimier un champ.
Dans ce cas-là, il existe plusieurs solutions -- mais la plupart
du temps, on exige que l'objet délégué passé en paramètre soit
alloué dynamiquement, et on en prend la responsibilité.
Typiquement, si on se sert du GC, il n'y a rien de particulier ;
sinon, l'utilisation d'un std::auto_ptr me semble convenir le
plus :

B::B( std::auto_ptr< A > a )
: myA( a )
{
}

où myA, dans la classe, a aussi le type std::auto_ptr< A >.
(C'est la façon « standard » de passer la responsibilité.)

L'utilisateur ferait donc :

B b( std::auto_ptr< A >( new AA ) ) ;

Une petite question tout de meme dans le cas où dans AA cette
fonction en appellerait une autre elle aussi dans AA:


class AA:public A{
protected:
fonction_specifique_a_AA();
public:


la_fonction_que_je_dois_implementer_pour_etre_un_A(){fonction_specifique_a_AA();};

}


fonction_spécifique_a_AA() ne sera pas définie dans mon objet
B::a. Cela étant ça ne devrait pas poser de probleme dans la
mesure où tout se compile bien et statiquement... Mais si
maintenant fonction_specifique_a_AA(); était virtuelle... vu
que la résolution est dynamique ça risque pas d'aller dans le
mur ?


Si tu définis un objet de type A, en B ou ailleurs, il a le type
A, et non le type AA. C'est donc les fonctions de la classe A
qui seront appelées. Toujours.

Si tu veux que le type B contient un objet de type inconnu, mais
qui dérive de A, il faut 1) que l'objet soit défini ailleurs,
parce qu'il faut bien connaître le type de l'objet lors de sa
définition, et 2) puisque l'objet est défini ailleurs, que le
type B le manipule avec un pointeur ou une référence (parce que
ce sont les seules façons à manipuler des objets qu'on n'a pas
définis).

Si, comme j'ai l'impression, il s'agit d'une implémentation du
modèle stragégie, la durée de vie de l'objet délégué n'a pas de
signification pour la sémantique du programme. Alors, la
meilleur solution est de se servir du glaneur de cellules.
Malheureusement, il n'est pas présent par défaut en C++, et si
tu n'as qu'une faible connaissance du C++ et du compilateur, il
se peut que tu ne sois pas en mesure de l'installer toi-même.
Alors, la solution standard avec std::auto_ptr s'impose, comme
ci-dessus. (Note bien qu'on peut se servir d'un std::auto_ptr
prèsque comme un pointeur normal ; que pour appeler ta fonction,
tu fais simplement myA->fonction(). Une des « complexités » de
C++ qui rend les programmes écrits en C++ plus simple, c'est la
possibilité de définir de nouveaux types qui se comportent comme
un pointeur.)

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
Fabien LE LEZ wrote:
On 9 Dec 2005 07:17:59 -0800, "meow" :


Rassurez moi, vous pratiquez le C++ depuis longtemps, non ?



Pour James (Kanze), c'est sûr -- je crois bien qu'il l'a vu
naître ;-)


À part ça, rassure-toi, en moins de 5 ans on arrive à acquerir
tous ces réflexes salutaires.


Pas vraiment, je n'étais pas en New Jersey à l'époque. Je n'ai
commencé à l'apprendre qu'en 1987 (et à m'en servir
professionnellement qu'en 1992).

Mais attention : ces réflexes salutaires dont tu parles ne sont
pas le propre du C++. Le premier réflexe ici, c'est de
reconnaître le modèle de conception qu'on veut mettre en jeu, et
ça, c'est une bonne réflexe, qu'on travaille en C++, en Java, ou
en Smalltalk ou n'importe quel autre langage. Et la deuxième
réflexe doit bien être de se renseigner quel est l'idiome
« standard » de mettre ce modèle en oeuvre dans le langage qu'on
utilise -- là aussi, le fait de se renseigner (plutôt que
d'essayer à réinventer soi-même) et le fait de vouloir utiliser
une solution standard que reconnaîtra d'autres programmeurs
expérimentés sont indépendants du langage.

En fait, si c'est le modèle stratégie qui l'intéresse, comme je
crois, la seule chose propre au C++, par rapport à certains
d'autres langages, c'est qu'aillant déterminé que la durée de
vie du délégué n'a pas de signification sémantique, dans le C++
sans GC, il est quand même obligé à s'en occuper, pour empècher
une fuite de mémoire. Mais l'idiome standard en C++ doit
utiliser std::auto_ptr, qui règle le problème.

Un problème potentiel, c'est qu'il y a beaucoup de documentation
sur ce genre de chose qui a été écrit avant la diffusion de la
norme, et qui donc n'adopte pas forcement les solutions basées
sur la norme. Dans "Design Patterns", par exemple, on utilise un
pointeur brut, et on se tait sur la question de la gestion de la
mémoire. (En revanche, et qui m'a surpris, compte tenu de la
date de parution du livre, il cite bien la possibilité
d'utiliser un paramètre template dans le cas où on peut savoir
au moment de la compilation quelle stratégie à adopter.)

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
Jean-Marc Bourguet wrote:
"meow" writes:


À part ça, rassure-toi, en moins de 5 ans on arrive à
acquerir tous ces réflexes salutaires.




'''O_o C'est vraiment sensé me rassurer ? 5 ans ?



Apres 5 ans, on a acquis le reflexe salutaire de se considerer
comme ignorant :-)


Après 5 ans, on se rend compte que le langage qu'on a appris a
tellement changé que ce n'est plus le même langage.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
James Kanze
Pierre Barbier de Reuille wrote:
Jean-Marc Bourguet wrote:


"meow" writes:



À part ça, rassure-toi, en moins de 5 ans on arrive à
acquerir tous ces réflexes salutaires.





'''O_o C'est vraiment sensé me rassurer ? 5 ans ?




Apres 5 ans, on a acquis le reflexe salutaire de se
considerer comme ignorant :-)



De toute façon, je ne connais pas de langages dans lequel on
peut être efficace en moins de 3 ans ! Même les langages à
apprentissage dit "rapide" (genre Python) demandent beaucoup
de temps avant d'arriver à voir, à l'avance, ce qui marchera
bien pour de gros projets.


Tout à fait. La grammaire du langage n'est qu'un détail, et
connaître toutes les subtilités de la norme C++ n'est ni
nécessaire ni suffisant pour écrire du bon C++. En revanche, à
quelque détails près, ce qu'il faut savoir est à peu près
identique dans tous les langages. Je serais même tempté de dire
que la complexité du langage C++ sert à rendre plus simple
beaucoup d'autres chose qu'il faut apprendre.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34




1 2