 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Fabien LE LEZ Guest
|
Posted: Fri Apr 28, 2006 5:06 am Post subject: private, private et private |
|
|
Bonjour,
Dans certaines classes, j'ai trois types de membres privés :
- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.
Exemple :
class B
{
private:
virtual void f()= 0;
};
class D: public B
{
private:
/*virtual*/ void f(); // Fonction qui vient de B.
void g(); // Fonction spécifique à D.
int une_variable_membre;
virtual void h(); // Fonction que les classes héritant de D.
};
J'aimerais savoir comment vous faites pour distinguer visuellement ces
différent types de membres privés.
Mettez-vous trois sections "private:" différentes, toujours dans le
même ordre ?
Y a-t-il d'autres méthodes ?
Question subsidiaire : spécifiez-vous le "virtual" même quand il est
implicite (comme pour B::f()) ?
Merci d'avance... |
|
| Back to top |
|
 |
kanze Guest
|
Posted: Fri Apr 28, 2006 8:06 am Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote:
| Quote: | Dans certaines classes, j'ai trois types de membres privés :
|
Moi aussi. Parfois même plus.
| Quote: | - des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
|
Disons plutôt, qui ne concerne que de l'implémentation. Des
membres pour lesquels la définition Java de private
conviendrait.
| Quote: | - des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
|
Prèsque toujours pûres, d'après mon expérience. (Mais si je me
souviens bien, c'était beaucoup moins vrai quand j'ai fait une
GUI.)
Mais pourquoi insistes-tu sur « nouvelles » ? Dans les classes
dérivées, les supplantations sont aussi privées, et j'aurais
tendance à les voir comme faisant partie de la même catégorie
des « privées ». (Mais c'est juste un point de vue.)
| Quote: | - des implémentations de fonctions virtuelles d'une classe de base.
|
Je ne vois pas ça comme une catégorie distincte de la
précédante. En revanche, j'ai aussi :
-- des declarations faites uniquement pour leurs effets de bord
sur l'interface publique -- la declaration d'un constructeur
de copie ou d'un opérateur d'affectation, surtout, mais
aussi dans certains cas précis, des surcharges des fonctions
publiques, pour empècher certaines conversions implicites
lors de leur appel. (Je crois ne jamais m'être servi de
cette possibilité, mais je sais que la technique existe.)
-- des declarations qui servent à certains amis seulement (par
rapport à celles qui sont réelement privées, et que même les
amis ne doivent pas utiliser).
| Quote: | Exemple :
class B
{
private:
virtual void f()= 0;
};
class D: public B
{
private:
/*virtual*/ void f(); // Fonction qui vient de B.
void g(); // Fonction spécifique à D.
int une_variable_membre;
virtual void h(); // Fonction que les classes héritant de D.
};
J'aimerais savoir comment vous faites pour distinguer
visuellement ces différent types de membres privés.
Mettez-vous trois sections "private:" différentes, toujours
dans le même ordre ?
|
Grosso modo, c'est ça. Bien que l'ordre n'est pas
rigueureusement fixe ; à part que les membres réelement privés
sont toujours derniers.
Pour les privés utilisés par les amis, j'ajoute un commentaire.
Sinon, c'est assez clair -- si la declaration qui suit private:
est une fonction virtuelle, par exemple, ou si c'est le
constructeur de copie. Ce qui donne :
class C
{
friend class F ;
public:
// ...
private:
virtual void f() = 0 ;
// ...
private:
C( C const& other ) ;
C& operator=( C const& other ) ;
private: // sauf pour F...
// ...
private: // really.
// ...
} ;
| Quote: | Y a-t-il d'autres méthodes ?
|
Je n'en connais pas, mais je n'en ai pas cherché non plus.
L'idiome des fonctions virtuelles privées et encore plus celui
du constructeur privé sans implémentation sont assez connus pour
qu'ils se passent de commentaires, a mon avis. (Dans le code
unitquement pour moi-même, j'hérite d'une classe
UncopiableObject pour supprimer la copie et l'affectation. Mais
dans le code que les autres doivent aussi comprendre, je préfère
l'idiome classique, immédiatement reconnu, à une astuce à moi,
aussi parlant soit-elle.)
| Quote: | Question subsidiaire : spécifiez-vous le "virtual" même quand
il est implicite (comme pour B::f()) ?
|
Toujours. Si je sais que la fonction est virtuelle, pourquoi
est-ce que je cacherais le fait de celui qui lit mon code ?
--
James Kanze GABI Software
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 |
|
| Back to top |
|
 |
Arnaud Meurgues Guest
|
Posted: Fri Apr 28, 2006 8:06 am Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote:
| Quote: | Mettez-vous trois sections "private:" différentes, toujours dans le
même ordre ?
|
Moi, c'est ce que j'aurais tendance à faire.
| Quote: | Question subsidiaire : spécifiez-vous le "virtual" même quand il est
implicite (comme pour B::f()) ?
|
Je crois qu'il y a différents points de vue. Était-ce Valentin
(quelqu'un sait ce qu'il devient ?) qui disait utiliser cette solution :
class A {
virtual void f();
};
class B : public A {
override void f();
};
avec une définition de override qui pouvait être soit :
#define override
soit
#define override virtual
Le problème est que si l'on met virtuel, on peut rendre virtuel une
fonction qui ne l'était pas et cacher la fonction non virtuelle de
départ. Ne rien mettre, en revanche, demandait de connaître la classe
mère pour savoir si la fonction était ou non virtuelle.
Mettre "override" permettait de signaler qu'on voulait redéfinir une
fonction sans pour autant introduire d'effet de bord.
L'intérêt de changer la définition de rien à virtual permettait aussi de
détecter des erreurs : si le comportement du programme change en
changeant la définition de override, c'est qu'on a fait une erreur sur
la virtualité des fonctions...
J'avais trouvé cela convainquant à l'époque et l'ai adopté pour mon
propre code.
--
Arnaud |
|
| Back to top |
|
 |
Fabien LE LEZ Guest
|
Posted: Fri Apr 28, 2006 10:06 am Post subject: Re: private, private et private |
|
|
On 28 Apr 2006 00:24:29 -0700, "kanze" <kanze@gabi-soft.fr>:
| Quote: | Prèsque toujours pûres, d'après mon expérience.
[...]
|
Oui.
| Quote: | Mais pourquoi insistes-tu sur « nouvelles » ?
|
Parce que bien souvent, je n'ai que deux types de fonctions,
complémentaires :
- des fonctions virtuelles pures
- des implémentations, qu'aucune classe dérivée ne change.
| Quote: | - des implémentations de fonctions virtuelles d'une classe de base.
Je ne vois pas ça comme une catégorie distincte de la
précédante.
|
Ben... C'est exactement l'inverse.
La catégorie précédente ajoute du boulot pour les classes dérivées ;
cette catégorie en enlève.
Typiquement, j'ai souvent un truc de ce genre :
class B
{
virtual int f (int)= 0;
};
class D: public B
{
virtual int g (int)= 0;
virtual int f (int x) { return 2-g(4*x); }
};
class DD: public D
{
virtual int g (int);
// On laisse f(int) telle quelle, elle convient très bien.
}; |
|
| Back to top |
|
 |
Hamiral Guest
|
Posted: Fri Apr 28, 2006 8:06 pm Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote:
| Quote: | Bonjour,
Dans certaines classes, j'ai trois types de membres privés :
- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.
|
Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je mets
toutes mes données liées uniquement à l'implémentation dans un pimpl, et
tout le reste en protected. Mais bon, je suis encore novice, donc ce n'est
probablement pas la meilleur chose finalement ...
--
Hamiral |
|
| Back to top |
|
 |
Fabien LE LEZ Guest
|
Posted: Fri Apr 28, 2006 10:06 pm Post subject: Re: private, private et private |
|
|
On Fri, 28 Apr 2006 21:21:09 +0200, Hamiral <hamiral (AT) hamham (DOT) fr>:
| Quote: | Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je mets
toutes mes données liées uniquement à l'implémentation dans un pimpl, et
tout le reste en protected.
|
Groumpf ?
Pimpl est un idiome certes très utile, mais relativement lourd, qu'on
ne doit donc utiliser que quand on en a besoin.
Et quand on a des hiérarchies de classes avec des fonctions virtuelles
en veux-tu en voilà, ça risque d'être assez pénible à gérer.
Quant à mettre "tout le reste en protected", j'ai du mal à voir ce que
tu veux dire.
Là encore, "protected" est utile, mais d'utilisation tout de même
assez limitée. Généralement, une section "protected:" contient assez
peu de fonctions (et aucune variable). |
|
| Back to top |
|
 |
Hamiral Guest
|
Posted: Sat Apr 29, 2006 8:06 am Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote:
| Quote: | On Fri, 28 Apr 2006 21:21:09 +0200, Hamiral <hamiral (AT) hamham (DOT) fr>:
Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je
mets toutes mes données liées uniquement à l'implémentation dans un pimpl,
et tout le reste en protected.
Groumpf ?
|
Je me suis probablement mal expliqué :
Je mets dans mon pimpl tout ce qui est relatif à l'implémentation de la
classe, et qui donc n'a pas besoin de faire partie de l'interface connue
par l'utilisateur. En général ce sont des données membres et des méthodes
qui servent à d'autres méthodes, et qui
1/ ne devraient pas être héritées
2/ ne devraient être connues que par la classe elle-même
Quant à tout ce que je mets en protected, c'est toutes les données et
méthodes qui ont un sens par rapport à ce que représente la classe : ces
données et méthodes peuvent être susceptibles d'être utilisées par une
classe dérivée.
| Quote: | Pimpl est un idiome certes très utile, mais relativement lourd, qu'on
ne doit donc utiliser que quand on en a besoin.
|
Relativement lourd ? Pas tant que cela je trouve ...
| Quote: | Et quand on a des hiérarchies de classes avec des fonctions virtuelles
en veux-tu en voilà, ça risque d'être assez pénible à gérer.
|
Dans ce cas-là effectivement, c'est plus difficile je suis d'accord.
| Quote: | Quant à mettre "tout le reste en protected", j'ai du mal à voir ce que
tu veux dire.
|
cf plus haut
| Quote: | Là encore, "protected" est utile, mais d'utilisation tout de même
assez limitée. Généralement, une section "protected:" contient assez
peu de fonctions (et aucune variable).
|
Là je ne suis pas d'accord du tout (cf plus haut).
--
Hamiral |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Sat Apr 29, 2006 10:07 am Post subject: Re: private, private et private |
|
|
Arnaud Meurgues wrote:
| Quote: | Fabien LE LEZ wrote:
|
[...]
| Quote: | Question subsidiaire : spécifiez-vous le "virtual" même quand
il est implicite (comme pour B::f()) ?
Je crois qu'il y a différents points de vue. Était-ce Valentin
(quelqu'un sait ce qu'il devient ?) qui disait utiliser cette
solution :
class A {
virtual void f();
};
class B : public A {
override void f();
};
avec une définition de override qui pouvait être soit :
#define override
soit
#define override virtual
Le problème est que si l'on met virtuel, on peut rendre
virtuel une fonction qui ne l'était pas et cacher la fonction
non virtuelle de départ. Ne rien mettre, en revanche,
demandait de connaître la classe mère pour savoir si la
fonction était ou non virtuelle.
Mettre "override" permettait de signaler qu'on voulait
redéfinir une fonction sans pour autant introduire d'effet de
bord.
L'intérêt de changer la définition de rien à virtual
permettait aussi de détecter des erreurs : si le comportement
du programme change en changeant la définition de override,
c'est qu'on a fait une erreur sur la virtualité des
fonctions...
J'avais trouvé cela convainquant à l'époque et l'ai adopté
pour mon propre code.
|
I'idée me plaît bien aussi. Seulement... que va penser celui qui
lit mon code et qui rencontre le symbole « override » à cet
endroit ? Ou pire, celui qui inclut mon en-tête, et qui a une
variable local qui s'appelle override ?
La première reflexion m'a fait abandonné ma classe
UncopiableObject, il y a bien quinze ans. Hériter publiquement
(donc, isA) d'une classe qui s'appelle UncopiableObject me
semble bien plus parlant qu'un constructeur de copie et un
opérateur d'affection privé, sans implémentation, mais ce
n'était pas l'idiome consacré, que tout le monde reconnaissait
au premier abord.
Si tu travailles dans une contexte où tu maîtrises toutes les
sources ET tous les programmeurs, que tu introduis la règle dans
tes règles de codages, que tu enforces son utilisation dans les
revues de code, et que tu assures bien que tous les programmeurs
connaissent et utilisent les règles de codage, c'est bien.
Sinon, j'aurais tendance à l'éviter. (Logiquement, ça doit
toujours être le cas dans une société qui produit du code
applicatif. Mais nous savons tous qu'il existe des sociétés où
le procès de développement n'est pas parfait. Et évidemment, si
tu produis les bibliothèques qui doivent servir en dehors de ta
société, tu ne peux absolument pas permettre une telle chose
dans les en-têtes que tu livres.)
--
James Kanze kanze.james (AT) neuf (DOT) 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 |
|
| Back to top |
|
 |
Fabien LE LEZ Guest
|
Posted: Sat Apr 29, 2006 10:07 am Post subject: Re: private, private et private |
|
|
On Sat, 29 Apr 2006 09:47:35 +0200, Hamiral <hamiral (AT) hamham (DOT) fr>:
| Quote: | Quant à tout ce que je mets en protected, c'est toutes les données et
méthodes qui ont un sens par rapport à ce que représente la classe
|
AMHA, c'est une erreur (du moins si je comprends bien ce que tu veux
dire).
Une section "protected", c'est la même chose qu'une section "public" :
c'est une interface. Les destinataires ne sont pas exactement les
mêmes (toutes les classes vs les classes dérivées), mais c'est un
détail -- le principe est le même[*].
Et une interface contient uniquement les fonctions explicitement
faites pour être appelées depuis l'extérieur. Et quasiment jamais de
variables[**].
[*] Note : une section "private" peut être soit une section qui
contient des membres privés, soit une interface -- envers les classes
amies, ou envers les classes dérivées par le truchement de fonctions
virtuelles.
[**] Il arrive que des variables membres soient accessibles à une
fonction extérieure à la classe ; mais dans ce cas, la classe n'a
généralement aucun membre protégé ou privé.
Et, bien souvent (dans mon code au moins), une telle classe n'a aucune
fonction non-const définie explicitement, sauf éventuellement le
constructeur. |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Sat Apr 29, 2006 10:07 am Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote:
| Quote: | On 28 Apr 2006 00:24:29 -0700, "kanze" <kanze@gabi-soft.fr>:
|
[...]
| Quote: | - des implémentations de fonctions virtuelles d'une classe de base.
Je ne vois pas ça comme une catégorie distincte de la
précédante.
Ben... C'est exactement l'inverse.
|
Oui, mais ce sont les mêmes fonctions. Pûre virtuelle, dans la
classe de base ; avec des implémentations dans la classe
dérivée.
Ou est-ce que tu penses au cas où la classe dérivée utilise le
modèle template, pour permettre encore de la customisation ?
Dans ce cas-là, je ferais bien une distinction entre les
fonctions que la classe dérivée implémente en tant
qu'implémentation de l'interface de base, et celle qu'elle
introduit pour permettre sa customisation à elle.
| Quote: | La catégorie précédente ajoute du boulot pour les classes
dérivées ; cette catégorie en enlève.
Typiquement, j'ai souvent un truc de ce genre :
class B
{
virtual int f (int)= 0;
};
class D: public B
{
virtual int g (int)= 0;
virtual int f (int x) { return 2-g(4*x); }
};
class DD: public D
{
virtual int g (int);
// On laisse f(int) telle quelle, elle convient très bien.
};
|
C'est bien le modèle template, alors. Et on est bien d'accord.
Seulement, j'avoue qu'il n'est pas particulièrement fréquent
chez moi. (Peut-être en fonction des domaines d'application --
quand j'ai fait ma charpente GUI, en Java, j'en avais bien.)
--
James Kanze kanze.james (AT) neuf (DOT) 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 |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Sat Apr 29, 2006 11:06 am Post subject: Re: private, private et private |
|
|
Hamiral wrote:
| Quote: | Fabien LE LEZ wrote:
Dans certaines classes, j'ai trois types de membres privés :
- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.
Moi, depuis que j'ai découvert l'idiome PIMPL (ici même
d'ailleurs), je mets toutes mes données liées uniquement à
l'implémentation dans un pimpl, et tout le reste en protected.
Mais bon, je suis encore novice, donc ce n'est probablement
pas la meilleur chose finalement ...
|
Certes, et c'est une bonne reflexe. Même s'il faut pouvoir
reconnaître des cas où il ne convient pas -- une implémentation
de complex avec cet idiome n'est peut-être pas la meilleur idée.
Mais il s'agit ici des fonctions, plus que des données, et il
s'agit surtout des éléments privés dont la présence doit être
connue de l'extérieur, pour une raison ou une autre.
Évidemment, on pourrait décider de declarer tout ce dont parle
Fabien protected. C'est ce que je fais en Java, par exemple, où
private a un sens différent qu'en C++. Et dans le cas des
fonctions virtuelles de l'idiome programmation par contrat, ça
rapproche plus à ce que fait Eiffel, et les origines de la
programmation par contrat. Mais d'après mon expérience, je
trouve que private convient mieux.
--
James Kanze kanze.james (AT) neuf (DOT) 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 |
|
| Back to top |
|
 |
Sylvain Guest
|
Posted: Sat Apr 29, 2006 12:06 pm Post subject: Re: private, private et private |
|
|
Fabien LE LEZ wrote on 28/04/2006 06:08:
| Quote: |
Dans certaines classes, j'ai trois types de membres privés :
- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
|
celles-ci sont claires/évidentes.
| Quote: | - des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
|
là je ne comprends pas le but.
en reprenant ton exemple:
class B {
private:
virtual void f() = null;
};
- B ne peux pas être instancée,
- toute classe fille devra définir f() mais ne pourra pas appeler la
méthode parent (pour une fille ayant un parent la définissant),
- les utilisateurs de B (et descendants) n'auront jamais accès à f()
dès lors:
qu'apporte la déclaration de f() à B et à ses descendants ? la méthode
étant purement interne, il me parait plus simple d'imaginer que chaque
sous-classe traitera de son comportement interne (masqué) comme bon lui
semble (comme cela ser ale plus pertinent pour chaque classe) -- peut
être certaines filles n'auront pas besoin du tout de f(), peut être que
d'autres voudront f1(), f2(), fn().
le sens d'une virtuelle privée continue à rester confus dans les
commentaires même de ton second exemple:
| Quote: | class D: public B {
private:
/*virtual*/ void f(); // Fonction qui vient de B.
virtual void h(); // Fonction que les classes héritant de D.
};
|
f() "qui vient de B" pourrait laisser comprendre qu'en effet il 'vient'
quelque chose, or ici il vient seulement une absence (une virtualité
pure) qui doit être bouchée (définie) pour pouvoir instancier B
h() "Fonction que les classes héritant de D" (il manque peut être une
suite) - ne pourront pas appeler, - pourront "surcharger" mais sans
avantage/différence par rapport à une déclaration propre de g(), h(), ...
un membre privé est implicitement masqué ("shadow-isé"), le rendre
virtual ne lève pas ce masquage, que reste-t-il alors de virtual?
Sylvain. |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Sun Apr 30, 2006 11:06 am Post subject: Re: private, private et private |
|
|
Sylvain wrote:
| Quote: | Fabien LE LEZ wrote on 28/04/2006 06:08:
Dans certaines classes, j'ai trois types de membres privés :
- des membres "réellement" privés, auxquels aucune autre
classe n'a accès ;
celles-ci sont claires/évidentes.
- des déclarations de nouvelles fonctions virtuelles
(éventuellement pures) ;
là je ne comprends pas le but.
|
C'est sans doute l'idiome de la programmation par contrat.
Certains vont jusqu'à dire que toute fonction virtuelle doit
être privée.
| Quote: | en reprenant ton exemple:
class B {
private:
virtual void f() = null;
|
« = 0 ».
| Quote: | };
- B ne peux pas être instancée,
|
C'est probablement le but, ou au moins une partie. B serait une
interface.
| Quote: | - toute classe fille devra définir f() mais ne pourra pas
appeler la méthode parent (pour une fille ayant un parent la
définissant),
|
Tout à fait. C'est virtuelle pûre -- c'est même probable qu'elle
n'a pas d'implémentation.
| Quote: | - les utilisateurs de B (et descendants) n'auront jamais accès à f()
|
Pas directement.
Fabien n'a montré que la partie essentielle à sa question ; le
modèle de la programmation par contrat est assez répandue qu'il
l'a sans doute supposé connu. Dans la pratique, évidemment, la
classe ne contient pas que des fonctions virtuelles pûres, mais
aussi des fonctions non-virtuelles publiques qui les appellent,
c-à-d :
class Base
{
public:
void f( char ch )
{
assert( isalpha( ch ) ) ;
doF( ch ) ;
}
Base* clone() const
{
Base* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}
// ...
private:
virtual void doF( char ch ) = 0 ;
virtual Base* doClone() = 0 ;
// ...
} ;
[...]
| Quote: | le sens d'une virtuelle privée continue à rester confus dans
les commentaires même de ton second exemple:
|
Le problème, c'est que Fabien a supposé qu'on reconnaît les
idiomes et les modèles d'où il a extrait ces exemples. Si on ne
connaît pas bien le GoF, ou les autres idiomes courants en C++,
c'est vrai que les extraits qu'il a présenté n'ont pas de sens.
Mais c'est uniquement parce qu'ils sont des extraits --
complétés avec les parties sans importance pour sa question, ce
sont des idiomes tout à fait classiques : celui ci-dessus
touche probablement quelque chose comme 90% ou plus des
hièrarchies polymorphiques dans une application bien écrite.
| Quote: | class D: public B {
private:
/*virtual*/ void f(); // Fonction qui vient de B.
virtual void h(); // Fonction que les classes héritant de D.
};
|
Tandis que celui-ci est bien moins fréquent. Mais je m'en suis
bien servi dans des applications GUI, et je crois que beaucoup
du code de Fabien concerne les GUI.
| Quote: | f() "qui vient de B" pourrait laisser comprendre qu'en effet
il 'vient' quelque chose, or ici il vient seulement une
absence (une virtualité pure) qui doit être bouchée (définie)
pour pouvoir instancier B
h() "Fonction que les classes héritant de D" (il manque peut
être une suite) - ne pourront pas appeler, - pourront
"surcharger" mais sans avantage/différence par rapport à une
déclaration propre de g(), h(), ...
|
En supposant qu'il s'agit d'une implémentation de B qui lui-même
utilise le modèle de template : conceptuellement, f() est
probablement « invisible » aux classes dérivées -- elle a été
implémentée, et le contrat de B a été rempli. En revanche, les
classes qui dérivent de D sont bien concernées par h() -- c'est
en supplantant h() qu'elles peuvent customiser D.
Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :
class GUIComponent
{
public :
void draw() const
{
assert( currentThread == GUIEventThread ) ;
doDraw() ;
}
private:
virtual void doDraw() = 0 ;
} ;
class GUIMessage : public GUIComponent
{
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const
{
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;
class GUIAlarm : public GUIMessage
{
private:
virtual Color* getBackgroundColor() const
{
return &red ;
}
} ;
| Quote: | un membre privé est implicitement masqué ("shadow-isé"), le
rendre virtual ne lève pas ce masquage, que reste-t-il alors
de virtual?
|
Je ne comprends pas exactement ce que tu essaies à dire. Privé
ou non est orthogonal au masquage -- et virtuelle ou non est
orthogonal aux deux.
--
James Kanze kanze.james (AT) neuf (DOT) 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 |
|
| Back to top |
|
 |
Sylvain Guest
|
Posted: Sun Apr 30, 2006 8:07 pm Post subject: Re: private, private et private |
|
|
James Kanze wrote on 30/04/2006 12:34:
| Quote: |
merci James, mais ... ma question ne remettait pas en cause des idiomes |
connus (même si je ne les cite pas à chaque point)
| Quote: | C'est sans doute l'idiome de la programmation par contrat.
|
dont ce fait
| Quote: | Certains vont jusqu'à dire que toute fonction virtuelle doit
être privée.
|
mais plus ce point et son caractère potentiellement excessif (cf plus bas).
| Quote: | Fabien n'a montré que la partie essentielle à sa question ; le
modèle de la programmation par contrat est assez répandue qu'il
l'a sans doute supposé connu. Dans la pratique, évidemment, la
classe ne contient pas que des fonctions virtuelles pûres, mais
aussi des fonctions non-virtuelles publiques qui les appellent,
|
nous sommes d'accord sur cet usage; note que je n'avais pas repris, ni
commenté le 3ième type de membres privés listés par Fabien "des
implémentations de fonctions virtuelles d'une classe de base", qui elles
répondent à 100% à cet idiome, comme dans l'exemple Buffer de Stroustrup
(§24.3.2.1) ou dans ta classe Base (pour sa méthode "f").
ta méthode clone soulève 2 problèmes (liés au langage ou à des bugs de
compilos) que tu contournes en introduisant un test sur le type
d'instance créé (pour détecter un oubli de surcharge de doClone dans une
classe fille), tel que tu le codes cela fonctionnera mais forcera un
dynamic_cast pour l'appelant s'il doit vraiment utiliser le clone comme
l'original (y compris pour des fonctions membres non déclarées dans la
classe de base).
or cela illustre une limitation du modifier private, puisque avec les
définitions suivantes (en supposant les csts)
class Base {
public:
Base* clone() {
Base* result = doClone();
assert(typeid(*result) == typeid(*this));
return result;
}
private:
virtual Base* doClone() = null;
};
class Derived : public Base {
private:
virtual Base* doClone() { return new Derived(*this); }
};
class Third : public Derived {
};
(excuses moi d'utiliser "= null" et non "= 0", j'ai cette habitude
depuis de très nombreuses années, je réserve "0" à des numériques, et
"null" à des ptr de fonctions ou des déclarations virtuelles.)
l'appel:
Third t;
t.clone();
sera exécuté; ie Base::clone() est appelée, qui appellera
Derived::clone() qui est pourtant private donc inaccessible à Third -
d'où le assert(typeid) au runtime pour pallier ce point.
c'est le problème 1, peut être lié à certains compilateurs uniquement.
le second problème vient du type retourné par clone; je préférerais les
définitions:
class Base {
public:
virtual Base* clone() = null;
};
class Derived : public Base {
public:
Derived* clone() { return new Derived(*this); }
};
class Third : public Derived {
public:
Third* clone() { return new Third(*this); }
};
ici un dynamic_cast serait économisé, mais (problème 2) certains
compilos ne supportent pas la surcharge du type d'une fonction.
| Quote: | class D: public B {
private:
/*virtual*/ void f(); // Fonction qui vient de B.
virtual void h(); // Fonction que les classes héritant de D.
};
En supposant qu'il s'agit d'une implémentation de B qui lui-même
utilise le modèle de template : conceptuellement, f() est
probablement « invisible » aux classes dérivées -- elle a été
implémentée, et le contrat de B a été rempli. En revanche, les
classes qui dérivent de D sont bien concernées par h() -- c'est
en supplantant h() qu'elles peuvent customiser D.
|
je suis évidemment d'accord avec ça -- je reformule ma question plus
simplement: est-on toujours vraiment certain de savoir ce qui a le droit
d'être appelé par une classe fille et ce qui doit être accessible qu'à
une seule classe ? ou encore n'est-il pas plus sage de se laisser la
possibilité d'invoquer des services parents ? (mes conjectures et
spéculations sur ce qui ne "les" regarde pas a très souvent été
contredit par l'expérience...).
en reprenant ton exemple de composant d'UI, est-ce vraiment
indispensable de chaque classe fille doive définir son
getBackgroundColor() ? j'aurais plutot envie d'avancer le contraire: une
classe parent (type Component) définit une couleur de fond standard
conforme au skin/thème/système et chaque controle comme chaque Container
(ayant une vocation spécifique) /peut/ (et non doit) redéfinir cette
couleur.
j'aurais plutôt envie de dire (de manière approximative, soit):
- une fonction virtuelle doit être privée si elle participe à une tache
simple définie une fois pour toute (ta méthode "f", les méthodes
overflow, undeflow de B.S.)
- une fonction virtuelle doit être protected si elle participe à une
tache complexe impliquant de nombreuses autres méthodes surchargées ou non.
| Quote: | Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :
class GUIComponent{
public :
void draw() const {
doDraw() ;
}
private:
virtual void doDraw() = 0 ;
} ;
class GUIMessage : public GUIComponent {
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const {
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;
|
private doDraw ne me parait pas pertinent car il prive de la possibilité
de le surcharger en l'invoquant dans la nouvelle classe - pourquoi
estimer que 'son' implémentation est la meilleure qui ne méritera jamais
le moindre ajout ?, pourquoi obliger l'auteur d'une nouvelle classe a
tout recoder ?
final doDraw ne laisse également perplexe, c'est la méthode draw qui est
vue de l'extérieur, non doDraw, un final draw forcerait l'utilisation de
doDraw et lui seul (et donc sa redéfinition complète); ici il suffira de
surcharger draw et qu'importe le caractère final de doDraw - ce qui
d'ailleurs n'est pas contradictoire avec ta présentation, on peut lire
en effet que draw() ne dépends que d'un doDraw final et que tant que
celui-ci est pertinent, il ne regarde personne de savoir ce qu'il fait
en interne; c'est plus le fait de rendre privé toutes les sous méthodes
utilitaires de doDraw (getBkClr, ...) qui me gène.
| Quote: | un membre privé est implicitement masqué ("shadow-isé"), le
rendre virtual ne lève pas ce masquage, que reste-t-il alors
de virtual?
Je ne comprends pas exactement ce que tu essaies à dire. Privé
ou non est orthogonal au masquage -- et virtuelle ou non est
orthogonal aux deux.
|
j'aurais du dire "ne renforce pas" la masquage (l'interdiction de
l'appel direct à la fonction d'une classe de base n'est pas renforcé par
l'ajout de virtual puisqu'il est déjà empéché par l'attribut private).
Sylvain. |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Sun Apr 30, 2006 9:06 pm Post subject: Re: private, private et private |
|
|
Sylvain wrote:
| Quote: | James Kanze wrote on 30/04/2006 12:34:
ta méthode clone soulève 2 problèmes (liés au langage ou à des
bugs de compilos) que tu contournes en introduisant un test
sur le type d'instance créé (pour détecter un oubli de
surcharge de doClone dans une classe fille), tel que tu le
codes cela fonctionnera mais forcera un dynamic_cast pour
l'appelant s'il doit vraiment utiliser le clone comme
l'original (y compris pour des fonctions membres non déclarées
dans la classe de base).
|
Pas nécessairement. Dans la pratique, chaque fois que je me suis
amené à fournir une fonction clone, le client se sert uniquement
des Base* (et des fonctions virtuelles).
Si le Derived fournit une interface étendue, il se peut qu'il
soit raisonable de fournir une deuxième fonction de clonage
(cloneDerived ?), disponible uniquement depuis Derived, et qui
renvoie un Derived*. Mais j'avoue que le cas ne s'est jamais
produit dans mon code à moi.
| Quote: | or cela illustre une limitation du modifier private, puisque
avec les définitions suivantes (en supposant les csts)
class Base {
public:
Base* clone() {
Base* result = doClone();
assert(typeid(*result) == typeid(*this));
return result;
}
private:
virtual Base* doClone() = null;
};
class Derived : public Base {
private:
virtual Base* doClone() { return new Derived(*this); }
};
class Third : public Derived {
};
l'appel:
Third t;
t.clone();
sera exécuté; ie Base::clone() est appelée, qui appellera
Derived::clone() qui est pourtant private donc inaccessible à
Third - d'où le assert(typeid) au runtime pour pallier ce
point. c'est le problème 1, peut être lié à certains
compilateurs uniquement.
|
C'est un exemple de l'erreur qu'on cherche à détecter,
justement. Le contrat de Base dit que toute classe qui en
dérive, même indirectement, doit founir une fonction doClone.
Third ne l'a pas fait -- c'est donc une erreur (une violation du
contrat).
| Quote: | le second problème vient du type retourné par clone; je
préférerais les définitions:
class Base {
public:
virtual Base* clone() = null;
};
class Derived : public Base {
public:
Derived* clone() { return new Derived(*this); }
};
class Third : public Derived {
public:
Third* clone() { return new Third(*this); }
};
ici un dynamic_cast serait économisé, mais (problème 2)
certains compilos ne supportent pas la surcharge du type d'une
fonction.
|
C'est une nouveauté introduite par la norme. Un compilateur
« moderne » doit le supporter. Mais évidemment, il y a trop
souvent de bonnes raisons de ne pas toujous se servir de la
dernière version du compilateur.
Comme j'ai dit ci-dessus, je ne suis pas réelement en convaincu
de l'utilité. Au moins dans mon style de programmation, je ne
m'en suis jamais senti le besoin. Mais enfin, c'est mon style de
programmation, et c'est peut-être aussi lié aux domaines
d'application où je travaille aussi -- des gens fort compétents
l'ont considéré assez utiles pour l'ajouter à la norme.
Et évidemment, elle rend l'utilisation de la programmation par
contrat impossible dans ce cas-ci. Alors, il faut faire le
choix. En fin de compte, la programmation par contrat n'est
jamais qu'un outil ; ce n'est pas une fin en soi. Si dans un cas
précis, elle coûte plus qu'elle en apporte...
| Quote: | class D: public B {
private:
/*virtual*/ void f(); // Fonction qui vient de B.
virtual void h(); // Fonction que les classes héritant de D.
};
En supposant qu'il s'agit d'une implémentation de B qui
lui-même utilise le modèle de template : conceptuellement,
f() est probablement « invisible » aux classes dérivées --
elle a été implémentée, et le contrat de B a été rempli. En
revanche, les classes qui dérivent de D sont bien concernées
par h() -- c'est en supplantant h() qu'elles peuvent
customiser D.
je suis évidemment d'accord avec ça -- je reformule ma
question plus simplement: est-on toujours vraiment certain de
savoir ce qui a le droit d'être appelé par une classe fille et
ce qui doit être accessible qu'à une seule classe ? ou encore
n'est-il pas plus sage de se laisser la possibilité d'invoquer
des services parents ? (mes conjectures et spéculations sur ce
qui ne "les" regarde pas a très souvent été contredit par
l'expérience...).
|
C'est une des grandes discussions dans la programmation orientée
objet. Mon expérience, c'est qu'au moins en C++, si tu n'as pas
prévue la dimension de customisation dans la classe de base,
elle ne marche pas. Simplement permettre la supplantation n'est
pas suffisante. Mais là aussi, c'est mon expérience, liée
peut-être aux domaines où je suis le plus actif, et ce n'est
certainement pas l'avis de tout le monde.
| Quote: | en reprenant ton exemple de composant d'UI, est-ce vraiment
indispensable de chaque classe fille doive définir son
getBackgroundColor() ?
|
Non. Si tu régardes l'exemple, tu verrais que j'ai fourni une
implémentation dans la classe qui a déclarée la fonction.
| Quote: | j'aurais plutot envie d'avancer le contraire: une classe
parent (type Component) définit une couleur de fond standard
conforme au skin/thème/système et chaque controle comme chaque
Container (ayant une vocation spécifique) /peut/ (et non doit)
redéfinir cette couleur.
|
Je me rendais compte même en le postant que l'exemple c'était
extrèmement simplifié -- dans la pratique, évidemment, la
gestion du « look and feel » est beaucoup plus complexe, et et
la classe Message et la classe qui en dérive chercherait en fait
leurs couleurs à partir des objets de gestion de « look and
feel », installer en fonction de l'environement et la
configuration de l'utilisateur. Mon but, c'était de créer un
exemple simple de l'utilisation du modèle template dans
l'implémentation d'une interface, non présenter un modèle de
comment concevoir un charpente de GUI.
| Quote: | j'aurais plutôt envie de dire (de manière approximative, soit):
- une fonction virtuelle doit être privée si elle participe à
une tache simple définie une fois pour toute (ta méthode "f",
les méthodes overflow, undeflow de B.S.)
- une fonction virtuelle doit être protected si elle participe
à une tache complexe impliquant de nombreuses autres méthodes
surchargées ou non.
|
Il y a là aussi beaucoup de désaccord entre les experts.
Personnellement, je préfère les fonctions privées dans la
plupart de ces cas, mais si le but est d'émuler la programmation
par contrat d'Eiffel (qui reste la référence), protected
conviendrait mieux. En ce qui concerne ta convention, dans la
mesure où 1) elle est documentée, et 2) tu t'en sers
systèmatiquement, je n'y vois pas de problème. Ce n'est pas
exactement la mienne, mais je ne crois pas qu'il y a une vérité
absolue dans cette question.
| Quote: | Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :
class GUIComponent{
public :
void draw() const {
doDraw() ;
}
private:
virtual void doDraw() = 0 ;
} ;
class GUIMessage : public GUIComponent {
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const {
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;
private doDraw ne me parait pas pertinent car il prive de la
possibilité de le surcharger en l'invoquant dans la nouvelle
classe - pourquoi estimer que 'son' implémentation est la
meilleure qui ne méritera jamais le moindre ajout ?, pourquoi
obliger l'auteur d'une nouvelle classe a tout recoder ?
|
Dans ce cas précis, c'est un bon point ; on pourrait en
discuter. Comme j'ai dis, j'ai juste cherché un exemple
rapidement que la plupart des gens pourraient comprendre. Je
crois quand même que si la garantie que fait GUIMessage, c'est
d'afficher d'une certaine façon, il faut qu'il contrôle la
fonction doDraw pour s'assurer que cette garantie est remplie.
Mais je reconnais que dans le cas des GUI, c'est difficile de
prévoyer tout -- par exemple, si une classe dérivée veut ajouter
un border, et que GUIMessage n'a pas prévu le cas. (Ce n'est pas
une bonne exemple non-plus, parce qu'un décorateur fait bien
l'affaire. Et on pourrait aussi arguer que c'est déjà une
caractèristique d'un composant d'avoir un arrière-plan et un
border -- en tout cas, Swing le fait, et le composant de base
remplit l'arrière-plan si le composant est opaque, et appelle
une fonction virtuelle distincte pour déssiner le border.)
| Quote: | final doDraw ne laisse également perplexe, c'est la méthode
draw qui est vue de l'extérieur,
|
Encore une fois, c'est le but de la manip. Afin que draw puisse
enforcer la règle que l'affichage n'a lieu que dans le thread
prévu.
| Quote: | non doDraw, un final draw forcerait l'utilisation de doDraw et
lui seul (et donc sa redéfinition complète);
|
Je ne suis pas sûr d'avoir compris ici. La fonction doDraw n'est
appelée que depuis draw, dans la base. Personne d'autre ne peut
l'appeler -- jamais. Il vérifie les préconditions, et appelle la
fonction virtuelle, supplantée dans la classe qui a pris la
responsibilité de l'affichage réel.
| Quote: | ici il suffira de surcharger draw et qu'importe le caractère
final de doDraw
|
Normalement, on ne manipule que les GUIComposant -- au moins
dans les endroits où draw serait appelé.
| Quote: | - ce qui d'ailleurs n'est pas contradictoire avec ta
présentation, on peut lire en effet que draw() ne dépends que
d'un doDraw final et que tant que celui-ci est pertinent, il
ne regarde personne de savoir ce qu'il fait en interne; c'est
plus le fait de rendre privé toutes les sous méthodes
utilitaires de doDraw (getBkClr, ...) qui me gène.
|
C'est un choix. Ici fortement conditionné par ce que je voulais
montrer. Si je concevais une charpente de GUI réele, je ferais
un analyse beaucoup plus profond de ce qui doit être privée, et
qui non. Intuitivement, je crois que getBackgroundColor serait
privée, mais sans un tel analyse, ce n'est pas une décision que
j'essaierai de défendre.
| Quote: | un membre privé est implicitement masqué ("shadow-isé"),
le rendre virtual ne lève pas ce masquage, que reste-t-il
alors de virtual?
Je ne comprends pas exactement ce que tu essaies à dire.
Privé ou non est orthogonal au masquage -- et virtuelle ou
non est orthogonal aux deux.
j'aurais du dire "ne renforce pas" la masquage (l'interdiction
de l'appel direct à la fonction d'une classe de base n'est pas
renforcé par l'ajout de virtual puisqu'il est déjà empéché par
l'attribut private).
|
C'est peut-être juste un problème de vocabulaire. J'ai
l'habitude de n'utiliser « masquer » que pour le fait qu'un nom
dans une classe dérivée masque toutes les instances de ce nom
dans des classes de base.
--
James Kanze kanze.james (AT) neuf (DOT) 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 |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|